@velvetmonkey/flywheel-memory 2.0.61 → 2.0.63

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 +70 -16
  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,
@@ -19011,6 +19048,9 @@ function getWatcherStatus() {
19011
19048
  var PRESETS = {
19012
19049
  // Presets
19013
19050
  minimal: ["search", "structure", "append", "frontmatter", "notes"],
19051
+ writer: ["search", "structure", "append", "frontmatter", "notes", "tasks"],
19052
+ agent: ["search", "structure", "append", "frontmatter", "notes", "memory"],
19053
+ researcher: ["search", "structure", "backlinks", "hubs", "paths"],
19014
19054
  full: [
19015
19055
  "search",
19016
19056
  "backlinks",
@@ -19025,17 +19065,18 @@ var PRESETS = {
19025
19065
  "append",
19026
19066
  "frontmatter",
19027
19067
  "notes",
19068
+ "note-ops",
19028
19069
  "git",
19029
19070
  "policy",
19030
19071
  "memory"
19031
19072
  ],
19032
- agent: ["search", "structure", "append", "frontmatter", "notes", "memory"],
19033
19073
  // Composable bundles
19034
19074
  graph: ["backlinks", "orphans", "hubs", "paths"],
19035
19075
  analysis: ["schema", "wikilinks"],
19036
19076
  tasks: ["tasks"],
19037
19077
  health: ["health"],
19038
- ops: ["git", "policy"]
19078
+ ops: ["git", "policy"],
19079
+ "note-ops": ["note-ops"]
19039
19080
  };
19040
19081
  var ALL_CATEGORIES = [
19041
19082
  "backlinks",
@@ -19051,13 +19092,14 @@ var ALL_CATEGORIES = [
19051
19092
  "append",
19052
19093
  "frontmatter",
19053
19094
  "notes",
19095
+ "note-ops",
19054
19096
  "git",
19055
19097
  "policy",
19056
19098
  "memory"
19057
19099
  ];
19058
19100
  var DEFAULT_PRESET = "full";
19059
19101
  function parseEnabledCategories() {
19060
- const envValue = process.env.FLYWHEEL_TOOLS?.trim();
19102
+ const envValue = (process.env.FLYWHEEL_TOOLS ?? process.env.FLYWHEEL_PRESET)?.trim();
19061
19103
  if (!envValue) {
19062
19104
  return new Set(PRESETS[DEFAULT_PRESET]);
19063
19105
  }
@@ -19128,11 +19170,12 @@ var TOOL_CATEGORY = {
19128
19170
  vault_replace_in_section: "append",
19129
19171
  // frontmatter (absorbed vault_add_frontmatter_field via only_if_missing)
19130
19172
  vault_update_frontmatter: "frontmatter",
19131
- // notes (CRUD)
19173
+ // notes (create only)
19132
19174
  vault_create_note: "notes",
19133
- vault_delete_note: "notes",
19134
- vault_move_note: "notes",
19135
- vault_rename_note: "notes",
19175
+ // note-ops (file management)
19176
+ vault_delete_note: "note-ops",
19177
+ vault_move_note: "note-ops",
19178
+ vault_rename_note: "note-ops",
19136
19179
  // git
19137
19180
  vault_undo_last_mutation: "git",
19138
19181
  // policy
@@ -19156,8 +19199,8 @@ var TOOL_CATEGORY = {
19156
19199
  // health (merge suggestions)
19157
19200
  suggest_entity_merges: "health",
19158
19201
  dismiss_merge_suggestion: "health",
19159
- // notes (entity merge)
19160
- merge_entities: "notes",
19202
+ // note-ops (entity merge)
19203
+ merge_entities: "note-ops",
19161
19204
  // memory (agent working memory)
19162
19205
  memory: "memory",
19163
19206
  recall: "memory",
@@ -19547,7 +19590,18 @@ async function runPostIndexWork(index) {
19547
19590
  serverLog("config", `Vault: ${flywheelConfig.vault_name}`);
19548
19591
  }
19549
19592
  if (process.env.FLYWHEEL_SKIP_EMBEDDINGS !== "true") {
19550
- if (hasEmbeddingsIndex()) {
19593
+ let modelChanged = false;
19594
+ if (hasEmbeddingsIndex() && needsEmbeddingRebuild()) {
19595
+ const oldModel = getStoredEmbeddingModel();
19596
+ serverLog("semantic", `Embedding model changed from ${oldModel} to ${getActiveModelId()}, rebuilding`);
19597
+ if (stateDb) {
19598
+ stateDb.db.exec("DELETE FROM note_embeddings");
19599
+ stateDb.db.exec("DELETE FROM entity_embeddings");
19600
+ }
19601
+ setEmbeddingsBuildState("none");
19602
+ modelChanged = true;
19603
+ }
19604
+ if (hasEmbeddingsIndex() && !modelChanged) {
19551
19605
  serverLog("semantic", "Embeddings already built, skipping full scan");
19552
19606
  } else {
19553
19607
  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.63",
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.63",
56
56
  "better-sqlite3": "^11.0.0",
57
57
  "chokidar": "^4.0.0",
58
58
  "gray-matter": "^4.0.3",