superlocalmemory 3.7.5 → 3.7.7
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 +27 -0
- package/README.md +2 -2
- package/package.json +2 -2
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/requirements.txt +1 -1
- package/plugin/skills/slm-recall/SKILL.md +4 -3
- package/plugin-src/manifest.json +1 -1
- package/plugin-src/requirements.txt +1 -1
- package/plugin-src/skills/slm-recall/SKILL.md +4 -3
- package/pyproject.toml +6 -6
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/cli/commands.py +169 -9
- package/src/superlocalmemory/cli/setup_wizard.py +53 -2
- package/src/superlocalmemory/core/backend_orchestrator.py +6 -1
- package/src/superlocalmemory/core/config.py +1 -1
- package/src/superlocalmemory/core/engine.py +3 -2
- package/src/superlocalmemory/core/engine_wiring.py +3 -1
- package/src/superlocalmemory/core/scale_engine.py +9 -1
- package/src/superlocalmemory/core/store_pipeline.py +1 -1
- package/src/superlocalmemory/hooks/before_web_hook.py +1 -1
- package/src/superlocalmemory/hooks/claude_code_hooks.py +1 -1
- package/src/superlocalmemory/infra/auth_middleware.py +5 -5
- package/src/superlocalmemory/mcp/_daemon_proxy.py +8 -10
- package/src/superlocalmemory/mcp/server.py +1 -0
- package/src/superlocalmemory/mcp/tools_active.py +11 -7
- package/src/superlocalmemory/mcp/tools_core.py +178 -20
- package/src/superlocalmemory/optimize/cache/centroid_store.py +21 -3
- package/src/superlocalmemory/optimize/cache/manager.py +7 -0
- package/src/superlocalmemory/optimize/cache/semantic.py +27 -10
- package/src/superlocalmemory/retrieval/engine.py +16 -8
- package/src/superlocalmemory/server/profile_runtime.py +384 -0
- package/src/superlocalmemory/server/recall_health.py +13 -7
- package/src/superlocalmemory/server/routes/chat.py +2 -2
- package/src/superlocalmemory/server/routes/helpers.py +9 -16
- package/src/superlocalmemory/server/routes/profiles.py +24 -14
- package/src/superlocalmemory/server/routes/v3_api.py +97 -20
- package/src/superlocalmemory/server/unified_daemon.py +261 -60
- package/src/superlocalmemory/storage/migration_runner.py +44 -0
- package/src/superlocalmemory/storage/migrations/M002_model_state_history.py +32 -3
- package/src/superlocalmemory/ui/index.html +32 -1
- package/src/superlocalmemory/ui/js/auto-settings.js +48 -0
- package/src/superlocalmemory/ui/js/memory-chat.js +2 -2
- package/src/superlocalmemory/ui/js/profiles.js +11 -2
- package/src/superlocalmemory/vector/lancedb_backend.py +42 -6
|
@@ -27,6 +27,50 @@ async function loadAutoSettings() {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
async function loadScopeSettings() {
|
|
31
|
+
try {
|
|
32
|
+
var response = await fetch('/api/v3/scope/config');
|
|
33
|
+
if (!response.ok) throw new Error('HTTP ' + response.status);
|
|
34
|
+
var data = await response.json();
|
|
35
|
+
var defaultScope = document.getElementById('settings-default-scope');
|
|
36
|
+
var shared = document.getElementById('settings-recall-shared');
|
|
37
|
+
var globalScope = document.getElementById('settings-recall-global');
|
|
38
|
+
if (defaultScope) defaultScope.value = data.default_scope || 'personal';
|
|
39
|
+
if (shared) shared.checked = data.recall_include_shared === true;
|
|
40
|
+
if (globalScope) globalScope.checked = data.recall_include_global === true;
|
|
41
|
+
} catch (error) {
|
|
42
|
+
var status = document.getElementById('settings-scope-status');
|
|
43
|
+
if (status) status.textContent = 'Could not load runtime visibility settings.';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function saveScopeSettings() {
|
|
48
|
+
var status = document.getElementById('settings-scope-status');
|
|
49
|
+
var payload = {
|
|
50
|
+
default_scope: document.getElementById('settings-default-scope')?.value || 'personal',
|
|
51
|
+
recall_include_shared: document.getElementById('settings-recall-shared')?.checked === true,
|
|
52
|
+
recall_include_global: document.getElementById('settings-recall-global')?.checked === true,
|
|
53
|
+
};
|
|
54
|
+
if (status) status.textContent = 'Applying to daemon...';
|
|
55
|
+
try {
|
|
56
|
+
var response = await fetch('/api/v3/scope/config', {
|
|
57
|
+
method: 'PUT',
|
|
58
|
+
headers: {'Content-Type': 'application/json'},
|
|
59
|
+
body: JSON.stringify(payload),
|
|
60
|
+
});
|
|
61
|
+
var data = await response.json();
|
|
62
|
+
if (!response.ok || data.success !== true) {
|
|
63
|
+
throw new Error(data.error || 'daemon rejected visibility settings');
|
|
64
|
+
}
|
|
65
|
+
if (status) status.textContent = 'Applied to the resident daemon.';
|
|
66
|
+
return true;
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if (status) status.textContent = 'Not applied: ' + error.message;
|
|
69
|
+
await loadScopeSettings();
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
30
74
|
function saveAutoCaptureConfig() {
|
|
31
75
|
var payload = {
|
|
32
76
|
enabled: document.getElementById('auto-capture-toggle')?.checked,
|
|
@@ -533,6 +577,9 @@ document.getElementById('settings-save-all')?.addEventListener('click', saveAllS
|
|
|
533
577
|
document.getElementById('settings-test-btn')?.addEventListener('click', testConnection);
|
|
534
578
|
document.getElementById('settings-emb-provider')?.addEventListener('change', updateEmbeddingUI);
|
|
535
579
|
document.getElementById('settings-emb-test-btn')?.addEventListener('click', testEmbeddingEndpoint);
|
|
580
|
+
['settings-default-scope', 'settings-recall-shared', 'settings-recall-global'].forEach(function(id) {
|
|
581
|
+
document.getElementById(id)?.addEventListener('change', saveScopeSettings);
|
|
582
|
+
});
|
|
536
583
|
|
|
537
584
|
// Mode radio buttons
|
|
538
585
|
document.querySelectorAll('input[name="settings-mode-radio"]').forEach(function(radio) {
|
|
@@ -542,6 +589,7 @@ document.querySelectorAll('input[name="settings-mode-radio"]').forEach(function(
|
|
|
542
589
|
// Load settings when the settings tab is shown
|
|
543
590
|
document.getElementById('settings-tab')?.addEventListener('shown.bs.tab', function() {
|
|
544
591
|
loadAutoSettings();
|
|
592
|
+
loadScopeSettings();
|
|
545
593
|
loadModeSettings();
|
|
546
594
|
loadEmbeddingSettings();
|
|
547
595
|
updateModeUI();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// SuperLocalMemory v3.4.1 — Ask My Memory Chat Interface
|
|
2
2
|
// Copyright (c) 2026 Varun Pratap Bhardwaj — AGPL-3.0-or-later
|
|
3
|
-
// SSE streaming chat grounded in
|
|
3
|
+
// SSE streaming chat grounded in multi-producer memory retrieval
|
|
4
4
|
|
|
5
5
|
// ============================================================================
|
|
6
6
|
// STATE
|
|
@@ -320,7 +320,7 @@ function _loadChatMode() {
|
|
|
320
320
|
+ '<i class="bi bi-info-circle"></i> <strong>Mode A</strong> — No LLM connected.<br>'
|
|
321
321
|
+ 'Chat returns raw memory retrieval results.<br>'
|
|
322
322
|
+ 'For AI-powered conversation, switch to <strong>Mode B</strong> (Ollama) or <strong>Mode C</strong> (Cloud) in the <strong>Settings</strong> tab.<br>'
|
|
323
|
-
+ '<br>You can also use the <strong>Recall Lab</strong> tab for full
|
|
323
|
+
+ '<br>You can also use the <strong>Recall Lab</strong> tab for full recall search.'
|
|
324
324
|
+ '</div>';
|
|
325
325
|
}
|
|
326
326
|
}
|
|
@@ -206,7 +206,9 @@ async function switchProfile(profileName) {
|
|
|
206
206
|
method: 'POST'
|
|
207
207
|
});
|
|
208
208
|
var data = await response.json();
|
|
209
|
-
|
|
209
|
+
var acknowledged = response.ok && data.success === true &&
|
|
210
|
+
data.active_profile === profileName && Number.isInteger(data.generation);
|
|
211
|
+
if (acknowledged) {
|
|
210
212
|
showToast('Switched to profile: ' + profileName);
|
|
211
213
|
loadProfiles();
|
|
212
214
|
loadStats();
|
|
@@ -226,11 +228,18 @@ async function switchProfile(profileName) {
|
|
|
226
228
|
if (typeof loadCompliance === 'function') loadCompliance();
|
|
227
229
|
var activeTab = document.querySelector('#mainTabs .nav-link.active');
|
|
228
230
|
if (activeTab) activeTab.click();
|
|
231
|
+
return true;
|
|
229
232
|
} else {
|
|
230
|
-
showToast('
|
|
233
|
+
showToast(data.detail || 'Daemon did not acknowledge the requested profile');
|
|
234
|
+
// Restore the selector from daemon runtime truth after any failure
|
|
235
|
+
// or mismatched acknowledgement.
|
|
236
|
+
loadProfiles();
|
|
237
|
+
return false;
|
|
231
238
|
}
|
|
232
239
|
} catch (error) {
|
|
233
240
|
console.error('Error switching profile:', error);
|
|
234
241
|
showToast('Error switching profile');
|
|
242
|
+
loadProfiles();
|
|
243
|
+
return false;
|
|
235
244
|
}
|
|
236
245
|
}
|
|
@@ -55,7 +55,11 @@ class LanceDBVectorBackend:
|
|
|
55
55
|
# Valid tier values (F-27: validated before interpolation)
|
|
56
56
|
VALID_TIERS: frozenset[str] = frozenset({"active", "warm", "cold", "archived"})
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
# Fallback embedding width when neither the caller nor an existing table
|
|
59
|
+
# specifies one. Matches the bundled nomic-embed-text-v1.5 model (768d).
|
|
60
|
+
DEFAULT_DIMENSION: int = 768
|
|
61
|
+
|
|
62
|
+
def __init__(self, db_path: str, dimension: int | None = None) -> None:
|
|
59
63
|
if not _LANCEDB_AVAILABLE:
|
|
60
64
|
raise LanceDBNotAvailable(
|
|
61
65
|
"LanceDB not installed. Run: pip install superlocalmemory[lancedb]"
|
|
@@ -63,22 +67,54 @@ class LanceDBVectorBackend:
|
|
|
63
67
|
path = Path(db_path)
|
|
64
68
|
path.mkdir(parents=True, exist_ok=True)
|
|
65
69
|
self._db_path = str(path)
|
|
70
|
+
# v3.7.6 (#72): the vector width is configurable so custom embedding
|
|
71
|
+
# endpoints (e.g. Qwen3-Embedding at 1024d) no longer collide with a
|
|
72
|
+
# hardcoded 768d schema/decode. An existing table's on-disk width always
|
|
73
|
+
# wins over the requested value to keep already-materialized stores
|
|
74
|
+
# readable after a config change.
|
|
75
|
+
self._dimension = int(dimension) if dimension else self.DEFAULT_DIMENSION
|
|
66
76
|
self._db = lancedb.connect(self._db_path) # type: ignore[union-attr]
|
|
67
77
|
self._table = self._open_or_create_table()
|
|
68
78
|
|
|
79
|
+
@property
|
|
80
|
+
def dimension(self) -> int:
|
|
81
|
+
"""Effective vector width used for this backend's schema and decode."""
|
|
82
|
+
return self._dimension
|
|
83
|
+
|
|
69
84
|
def _open_or_create_table(self):
|
|
70
|
-
"""Open existing table or create
|
|
85
|
+
"""Open existing table (adopting its width) or create one at self._dimension."""
|
|
71
86
|
try:
|
|
72
|
-
|
|
87
|
+
table = self._db.open_table("embeddings")
|
|
73
88
|
except Exception:
|
|
74
89
|
import pyarrow as pa
|
|
75
90
|
schema = pa.schema([
|
|
76
91
|
pa.field("fact_id", pa.string(), nullable=False),
|
|
77
|
-
pa.field(
|
|
92
|
+
pa.field(
|
|
93
|
+
"vector",
|
|
94
|
+
pa.list_(pa.float32(), list_size=self._dimension),
|
|
95
|
+
nullable=False,
|
|
96
|
+
),
|
|
78
97
|
pa.field("tier", pa.string(), nullable=False),
|
|
79
98
|
pa.field("profile_id", pa.string(), nullable=False),
|
|
80
99
|
])
|
|
81
100
|
return self._db.create_table("embeddings", schema=schema)
|
|
101
|
+
# Adopt the persisted width so decode/validation matches what is stored.
|
|
102
|
+
existing = self._table_vector_width(table)
|
|
103
|
+
if existing:
|
|
104
|
+
self._dimension = existing
|
|
105
|
+
return table
|
|
106
|
+
|
|
107
|
+
@staticmethod
|
|
108
|
+
def _table_vector_width(table) -> int | None:
|
|
109
|
+
"""Best-effort read of the persisted vector list width, or None."""
|
|
110
|
+
try:
|
|
111
|
+
field = table.schema.field("vector")
|
|
112
|
+
list_size = getattr(field.type, "list_size", None)
|
|
113
|
+
if isinstance(list_size, int) and list_size > 0:
|
|
114
|
+
return list_size
|
|
115
|
+
except Exception: # pragma: no cover — schema introspection is best-effort
|
|
116
|
+
logger.debug("Could not read persisted vector width", exc_info=True)
|
|
117
|
+
return None
|
|
82
118
|
|
|
83
119
|
def close(self) -> None:
|
|
84
120
|
"""Release this backend's native table and connection references."""
|
|
@@ -230,13 +266,13 @@ class LanceDBVectorBackend:
|
|
|
230
266
|
F-33: Validates dimension and L2 norm.
|
|
231
267
|
sqlite-vec stores vectors as raw float32 little-endian bytes.
|
|
232
268
|
"""
|
|
233
|
-
expected_bytes =
|
|
269
|
+
expected_bytes = self._dimension * 4
|
|
234
270
|
if len(blob) != expected_bytes:
|
|
235
271
|
raise ValueError(
|
|
236
272
|
f"Unexpected vector blob size: {len(blob)} (expected {expected_bytes})"
|
|
237
273
|
)
|
|
238
274
|
|
|
239
|
-
vec = list(struct.unpack(f"{
|
|
275
|
+
vec = list(struct.unpack(f"{self._dimension}f", blob))
|
|
240
276
|
|
|
241
277
|
# F-33: Validate non-zero
|
|
242
278
|
norm = sum(v * v for v in vec) ** 0.5
|