@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.
- package/dist/index.js +70 -16
- 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,
|
|
@@ -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 (
|
|
19173
|
+
// notes (create only)
|
|
19132
19174
|
vault_create_note: "notes",
|
|
19133
|
-
|
|
19134
|
-
|
|
19135
|
-
|
|
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
|
-
//
|
|
19160
|
-
merge_entities: "
|
|
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
|
-
|
|
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.
|
|
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": "
|
|
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",
|