superlocalmemory 3.8.0 → 3.8.2
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 +112 -0
- package/README.md +32 -120
- package/package.json +9 -2
- package/plugin/.claude-plugin/plugin.json +1 -2
- package/plugin/CLAUDE.md +3 -3
- package/plugin/agents/slm-governance-advisor.md +1 -1
- package/plugin/agents/slm-loop-runner.md +1 -1
- package/plugin/agents/slm-memory-advisor.md +2 -2
- package/plugin/agents/slm-optimize-advisor.md +1 -1
- package/plugin/requirements.txt +1 -1
- package/plugin/skills/slm-cache/SKILL.md +1 -1
- package/plugin/skills/slm-compress/SKILL.md +1 -1
- package/plugin/skills/slm-governance/SKILL.md +1 -1
- package/plugin/skills/slm-graph/SKILL.md +1 -1
- package/plugin/skills/slm-loop/SKILL.md +1 -1
- package/plugin/skills/slm-mesh/SKILL.md +1 -1
- package/plugin/skills/slm-profile/SKILL.md +1 -1
- package/plugin/skills/slm-recall/SKILL.md +3 -5
- package/plugin/skills/slm-remember/SKILL.md +1 -1
- package/plugin/skills/slm-scope/SKILL.md +1 -1
- package/plugin/skills/slm-session/SKILL.md +1 -1
- package/plugin/skills/slm-status/SKILL.md +1 -1
- package/plugin-src/rules/AGENTS.md +2 -1
- package/plugin-src/skills/slm-cache/SKILL.md +1 -1
- package/plugin-src/skills/slm-compress/SKILL.md +1 -1
- package/plugin-src/skills/slm-graph/SKILL.md +1 -1
- package/plugin-src/skills/slm-recall/SKILL.md +3 -5
- package/plugin-src/skills/slm-remember/SKILL.md +1 -1
- package/plugin-src/skills/slm-session/SKILL.md +1 -1
- package/plugin-src/skills/slm-status/SKILL.md +1 -1
- package/pyproject.toml +2 -1
- package/scripts/postinstall.js +7 -1
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/cli/commands.py +494 -9
- package/src/superlocalmemory/cli/daemon.py +7 -0
- package/src/superlocalmemory/cli/loop_cmd.py +2 -7
- package/src/superlocalmemory/cli/main.py +72 -7
- package/src/superlocalmemory/cli/setup_wizard.py +142 -16
- package/src/superlocalmemory/cli/version_banner.py +17 -3
- package/src/superlocalmemory/core/backend_orchestrator.py +18 -16
- package/src/superlocalmemory/core/component_healer.py +144 -0
- package/src/superlocalmemory/core/component_registry.py +487 -0
- package/src/superlocalmemory/core/config.py +21 -0
- package/src/superlocalmemory/core/embedding_worker.py +4 -5
- package/src/superlocalmemory/core/embeddings.py +132 -45
- package/src/superlocalmemory/core/engine.py +29 -22
- package/src/superlocalmemory/core/engine_ingestion.py +332 -45
- package/src/superlocalmemory/core/ingestion_command.py +154 -25
- package/src/superlocalmemory/core/injection.py +12 -7
- package/src/superlocalmemory/core/maintenance.py +43 -0
- package/src/superlocalmemory/core/maintenance_scheduler.py +44 -6
- package/src/superlocalmemory/core/recall_pipeline.py +42 -4
- package/src/superlocalmemory/core/store_pipeline.py +195 -20
- package/src/superlocalmemory/hooks/hook_handlers.py +6 -1
- package/src/superlocalmemory/hooks/portable_kit.py +34 -2
- package/src/superlocalmemory/learning/model_rollback.py +3 -0
- package/src/superlocalmemory/learning/ranker_retrain_online.py +2 -0
- package/src/superlocalmemory/learning/reward.py +50 -0
- package/src/superlocalmemory/learning/source_quality.py +523 -1
- package/src/superlocalmemory/loops/ledger.py +25 -5
- package/src/superlocalmemory/mcp/_daemon_proxy.py +6 -2
- package/src/superlocalmemory/mcp/_pool_adapter.py +4 -1
- package/src/superlocalmemory/mcp/server.py +11 -30
- package/src/superlocalmemory/mcp/tools_active.py +1 -1
- package/src/superlocalmemory/mcp/tools_core.py +21 -5
- package/src/superlocalmemory/mcp/tools_learning.py +2 -2
- package/src/superlocalmemory/retrieval/bridge_discovery.py +14 -0
- package/src/superlocalmemory/retrieval/engine.py +53 -21
- package/src/superlocalmemory/retrieval/reranker.py +3 -4
- package/src/superlocalmemory/retrieval/spreading_activation.py +68 -38
- package/src/superlocalmemory/server/config_file.py +90 -0
- package/src/superlocalmemory/server/origin.py +50 -0
- package/src/superlocalmemory/server/routes/backup.py +293 -70
- package/src/superlocalmemory/server/routes/behavioral.py +342 -61
- package/src/superlocalmemory/server/routes/brain.py +57 -16
- package/src/superlocalmemory/server/routes/config_api.py +84 -82
- package/src/superlocalmemory/server/routes/entity.py +100 -23
- package/src/superlocalmemory/server/routes/evolution.py +103 -100
- package/src/superlocalmemory/server/routes/learning.py +286 -105
- package/src/superlocalmemory/server/routes/learning_telemetry.py +153 -0
- package/src/superlocalmemory/server/routes/memories.py +8 -3
- package/src/superlocalmemory/server/routes/mesh.py +121 -32
- package/src/superlocalmemory/server/routes/ratelimit.py +33 -25
- package/src/superlocalmemory/server/routes/stats.py +93 -155
- package/src/superlocalmemory/server/routes/token.py +3 -13
- package/src/superlocalmemory/server/routes/v3_api.py +184 -20
- package/src/superlocalmemory/server/unified_daemon.py +732 -41
- package/src/superlocalmemory/storage/embedding_migrator.py +235 -0
- package/src/superlocalmemory/storage/migration_runner.py +79 -1
- package/src/superlocalmemory/storage/migrations/M010_evolution_config.py +5 -0
- package/src/superlocalmemory/storage/migrations/M028_fact_entity_associations.py +270 -0
- package/src/superlocalmemory/storage/migrations/M029_behavioral_history_indexes.py +137 -0
- package/src/superlocalmemory/storage/migrations/M030_entity_explorer_indexes.py +93 -0
- package/src/superlocalmemory/storage/migrations/__init__.py +4 -0
- package/src/superlocalmemory/storage/schema.py +49 -1
- package/src/superlocalmemory/storage/schema_v32.py +2 -0
- package/src/superlocalmemory/storage/schema_v347.py +4 -0
- package/src/superlocalmemory/ui/index.html +6 -8
- package/src/superlocalmemory/ui/js/core.js +52 -9
- package/src/superlocalmemory/ui/js/dashboard.js +169 -82
- package/src/superlocalmemory/ui/js/od-backup.js +156 -65
- package/src/superlocalmemory/ui/js/od-brain.js +88 -51
- package/src/superlocalmemory/ui/js/od-components.js +147 -0
- package/src/superlocalmemory/ui/js/od-entities.js +65 -22
- package/src/superlocalmemory/ui/js/od-graph.js +46 -4
- package/src/superlocalmemory/ui/js/od-health.js +18 -0
- package/src/superlocalmemory/ui/js/od-memories.js +84 -5
- package/src/superlocalmemory/ui/js/od-mesh.js +23 -9
- package/src/superlocalmemory/ui/js/od-operations.js +36 -0
- package/src/superlocalmemory/ui/js/od-settings.js +186 -63
- package/src/superlocalmemory/ui/js/od-shell.js +249 -33
- package/src/superlocalmemory/ui/js/od-skills.js +44 -17
- package/src/superlocalmemory/ui/js/settings.js +15 -1
- package/plugin-src/.mcp.json +0 -12
- package/plugin-src/agents/slm-governance-advisor.md +0 -80
- package/plugin-src/agents/slm-loop-runner.md +0 -71
- package/plugin-src/agents/slm-memory-advisor.md +0 -49
- package/plugin-src/agents/slm-optimize-advisor.md +0 -44
- package/plugin-src/commands/slm-loop.md +0 -31
- package/plugin-src/hooks/.gitkeep +0 -0
- package/plugin-src/hooks/hooks.json +0 -102
- package/plugin-src/manifest.json +0 -30
- package/plugin-src/requirements.txt +0 -1
- package/plugin-src/rules/CLAUDE.md.fragment +0 -44
- package/plugin-src/scripts/ensure-venv.bat +0 -122
- package/plugin-src/scripts/ensure-venv.sh +0 -105
- package/plugin-src/scripts/slm-launch +0 -62
- package/plugin-src/scripts/slm-launch.bat +0 -23
- package/plugin-src/settings.json +0 -25
- package/plugin-src/skills/slm-governance/SKILL.md +0 -248
- package/plugin-src/skills/slm-loop/SKILL.md +0 -99
- package/plugin-src/skills/slm-mesh/SKILL.md +0 -282
- package/plugin-src/skills/slm-profile/SKILL.md +0 -148
- package/plugin-src/skills/slm-scope/SKILL.md +0 -176
|
@@ -267,6 +267,13 @@ def daemon_request(
|
|
|
267
267
|
if capability is not None:
|
|
268
268
|
headers["X-SLM-Daemon-Capability"] = capability
|
|
269
269
|
headers["X-SLM-Target-Instance"] = descriptor.instance_id
|
|
270
|
+
# Daemon ownership proves that this CLI targets the local instance; it
|
|
271
|
+
# does not replace a dashboard user's profile-scoped authorization in
|
|
272
|
+
# governed workspaces. The user opts in by supplying an explicit
|
|
273
|
+
# session through the process environment (never logged or persisted).
|
|
274
|
+
user_session = os.environ.get("SLM_USER_SESSION", "").strip()
|
|
275
|
+
if user_session:
|
|
276
|
+
headers["X-SLM-User-Session"] = user_session
|
|
270
277
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
|
271
278
|
resp = urllib.request.urlopen(req, timeout=timeout_seconds)
|
|
272
279
|
return json.loads(resp.read().decode())
|
|
@@ -19,11 +19,11 @@ store rooted at the active data root (``SLM_DATA_DIR`` or
|
|
|
19
19
|
from __future__ import annotations
|
|
20
20
|
|
|
21
21
|
import json
|
|
22
|
-
import os
|
|
23
22
|
from argparse import Namespace
|
|
24
23
|
from pathlib import Path
|
|
25
24
|
from typing import Any
|
|
26
25
|
|
|
26
|
+
from superlocalmemory.infra.data_root import canonical_data_root
|
|
27
27
|
from superlocalmemory.loops import (
|
|
28
28
|
Bounds,
|
|
29
29
|
LapResult,
|
|
@@ -35,12 +35,7 @@ from superlocalmemory.loops import (
|
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
def _data_root() -> Path:
|
|
38
|
-
|
|
39
|
-
os.environ.get("SLM_DATA_DIR")
|
|
40
|
-
or os.environ.get("SL_MEMORY_PATH")
|
|
41
|
-
or os.environ.get("SLM_HOME")
|
|
42
|
-
)
|
|
43
|
-
return Path(value).expanduser() if value else Path.home() / ".superlocalmemory"
|
|
38
|
+
return canonical_data_root()
|
|
44
39
|
|
|
45
40
|
|
|
46
41
|
class _FailOpenLedger:
|
|
@@ -82,6 +82,8 @@ _NO_DAEMON_COMMANDS = {
|
|
|
82
82
|
"optimize", "cache", "compress", "help-optimize",
|
|
83
83
|
# Bounded loops use an in-process engine store, not the daemon.
|
|
84
84
|
"loop",
|
|
85
|
+
# v3.8.2 super-help is pure text — never touch the daemon.
|
|
86
|
+
"help",
|
|
85
87
|
# Lifecycle orchestration must run before any global auto-start hook.
|
|
86
88
|
"serve", "restart",
|
|
87
89
|
}
|
|
@@ -114,12 +116,17 @@ def main() -> None:
|
|
|
114
116
|
or any(arg in {"-h", "--help"} for arg in sys.argv[1:])
|
|
115
117
|
or (len(sys.argv) == 2 and sys.argv[1] in {"-v", "--version"})
|
|
116
118
|
)
|
|
119
|
+
_is_connect_dry_run = (
|
|
120
|
+
len(sys.argv) >= 2
|
|
121
|
+
and sys.argv[1] == "connect"
|
|
122
|
+
and "--dry-run" in sys.argv[2:]
|
|
123
|
+
)
|
|
117
124
|
|
|
118
125
|
# WP-07: lazy first-run init — runs after hook/mcp fast-paths so stdout
|
|
119
126
|
# is never polluted on those paths (CRIT-3, MCP JSON-RPC purity).
|
|
120
127
|
# Guarded: any failure must not crash the CLI (AC4).
|
|
121
128
|
_is_mcp_cmd = len(sys.argv) >= 2 and sys.argv[1] == "mcp"
|
|
122
|
-
if not _is_mcp_cmd and not _is_metadata_cmd:
|
|
129
|
+
if not _is_mcp_cmd and not _is_metadata_cmd and not _is_connect_dry_run:
|
|
123
130
|
try:
|
|
124
131
|
from superlocalmemory.cli._lazy_init import _ensure_initialized
|
|
125
132
|
_ensure_initialized()
|
|
@@ -139,7 +146,7 @@ def main() -> None:
|
|
|
139
146
|
|
|
140
147
|
# One-time post-upgrade banner — silent for fresh installs and
|
|
141
148
|
# same-version runs. Guarded against I/O errors internally.
|
|
142
|
-
if not _is_mcp_stdio and not _is_metadata_cmd:
|
|
149
|
+
if not _is_mcp_stdio and not _is_metadata_cmd and not _is_connect_dry_run:
|
|
143
150
|
from superlocalmemory.cli.version_banner import check_and_emit_upgrade_banner
|
|
144
151
|
if check_and_emit_upgrade_banner(_ver):
|
|
145
152
|
# First post-upgrade invocation: apply the data-dir migration if
|
|
@@ -278,6 +285,37 @@ def main() -> None:
|
|
|
278
285
|
db_scale_p.add_argument("--stage-id", help="Stage identifier required by verify/promote")
|
|
279
286
|
db_scale_p.add_argument("--backup-id", help="Backup identifier required by rollback")
|
|
280
287
|
|
|
288
|
+
db_reembed_p = db_sub.add_parser(
|
|
289
|
+
"reembed",
|
|
290
|
+
help="Backfill NULL embeddings (facts never embedded)",
|
|
291
|
+
)
|
|
292
|
+
db_reembed_p.add_argument(
|
|
293
|
+
"--missing-only",
|
|
294
|
+
action="store_true",
|
|
295
|
+
default=True,
|
|
296
|
+
dest="missing_only",
|
|
297
|
+
help="Only embed facts with NULL embedding (default and only mode)",
|
|
298
|
+
)
|
|
299
|
+
db_reembed_p.add_argument(
|
|
300
|
+
"--all-profiles",
|
|
301
|
+
action="store_true",
|
|
302
|
+
default=False,
|
|
303
|
+
dest="all_profiles",
|
|
304
|
+
help="Process all profiles (default: active profile only)",
|
|
305
|
+
)
|
|
306
|
+
db_reembed_p.add_argument(
|
|
307
|
+
"--limit",
|
|
308
|
+
type=int,
|
|
309
|
+
default=None,
|
|
310
|
+
metavar="N",
|
|
311
|
+
help="Maximum facts to embed per run (default: no limit)",
|
|
312
|
+
)
|
|
313
|
+
db_reembed_p.add_argument(
|
|
314
|
+
"--json",
|
|
315
|
+
action="store_true",
|
|
316
|
+
help="Output structured JSON",
|
|
317
|
+
)
|
|
318
|
+
|
|
281
319
|
# -- Mesh inspection (v3.7.9, M-03) --------------------------------
|
|
282
320
|
mesh_p = sub.add_parser("mesh", help="Inspect the local agent mesh (status/peers)")
|
|
283
321
|
mesh_p.add_argument(
|
|
@@ -322,9 +360,11 @@ def main() -> None:
|
|
|
322
360
|
)
|
|
323
361
|
recall_p.add_argument(
|
|
324
362
|
"--fast", action="store_true",
|
|
325
|
-
help="
|
|
326
|
-
"
|
|
327
|
-
"
|
|
363
|
+
help="Force-skip the internal agentic verification round (all six retrieval "
|
|
364
|
+
"channels + reranker still run). This is already the default (client-driven "
|
|
365
|
+
"agentic): SLM returns fast local results + confidence signals and lets the "
|
|
366
|
+
"calling LLM re-query on low confidence. Pass --fast to guarantee the fast "
|
|
367
|
+
"path even where the server is configured for internal agentic.",
|
|
328
368
|
)
|
|
329
369
|
# v3.6.15: shared memory is opt-in. Unset (None) → resolve the configured
|
|
330
370
|
# default (recall_include_global/shared, both False by default). Explicit
|
|
@@ -346,6 +386,15 @@ def main() -> None:
|
|
|
346
386
|
"--no-shared", dest="include_shared", action="store_false", default=None,
|
|
347
387
|
help="Exclude shared-scope facts from retrieval",
|
|
348
388
|
)
|
|
389
|
+
# v3.8.2 client-driven agentic: agents parsing --json should ACT on the
|
|
390
|
+
# confidence signals rather than expect a server-side LLM to reformulate.
|
|
391
|
+
recall_p.epilog = (
|
|
392
|
+
"AGENT GUIDANCE (--json): recall returns fast local results plus "
|
|
393
|
+
"confidence signals. If 'no_confident_match' is true or "
|
|
394
|
+
"'answer_confidence' is low, rewrite the query into 1-3 more specific "
|
|
395
|
+
"sub-queries and call recall again before concluding nothing was found. "
|
|
396
|
+
"Confident match -> use it directly."
|
|
397
|
+
)
|
|
349
398
|
|
|
350
399
|
forget_p = sub.add_parser("forget", help="Delete memories matching a query (fuzzy)")
|
|
351
400
|
forget_p.add_argument("query", nargs="?", default=None, help="Query to match for deletion. Optional with --dry-run (previews all memories).")
|
|
@@ -392,6 +441,21 @@ def main() -> None:
|
|
|
392
441
|
"--quick", action="store_true",
|
|
393
442
|
help="Run only the fast checks (deps + config); skip daemon/embedding probes",
|
|
394
443
|
)
|
|
444
|
+
doctor_p.add_argument(
|
|
445
|
+
"--fix", action="store_true",
|
|
446
|
+
help="Auto-repair fixable components (re-download missing models, "
|
|
447
|
+
"install sqlite-vec) before checking, then report",
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
# v3.8.2 super-help — grouped overview of every command + focused topics.
|
|
451
|
+
help_p = sub.add_parser(
|
|
452
|
+
"help",
|
|
453
|
+
help="Grouped overview of all commands + topics (modes, config, self-heal)",
|
|
454
|
+
)
|
|
455
|
+
help_p.add_argument(
|
|
456
|
+
"topic", nargs="?",
|
|
457
|
+
help="Optional topic: modes | config | self-heal",
|
|
458
|
+
)
|
|
395
459
|
|
|
396
460
|
# LLD-06 §6.6 — `slm wrap <agent> [args...]` activates the Optimize
|
|
397
461
|
# proxy for a specific agent. Supported agents: claude, claude-settings,
|
|
@@ -816,8 +880,9 @@ def main() -> None:
|
|
|
816
880
|
sys.exit(0)
|
|
817
881
|
|
|
818
882
|
# V3.3.19: Auto-trigger setup wizard on first use
|
|
819
|
-
|
|
820
|
-
|
|
883
|
+
if not (args.command == "connect" and getattr(args, "dry_run", False)):
|
|
884
|
+
from superlocalmemory.cli.setup_wizard import check_first_use
|
|
885
|
+
check_first_use(args.command)
|
|
821
886
|
|
|
822
887
|
# V3.4.4: Auto-start daemon for all commands that need it.
|
|
823
888
|
# SLM is always-on — close laptop, reboot, crash: daemon auto-recovers.
|
|
@@ -100,6 +100,25 @@ def _get_ram_gb() -> float:
|
|
|
100
100
|
return 0.0
|
|
101
101
|
|
|
102
102
|
|
|
103
|
+
def _ollama_available() -> bool:
|
|
104
|
+
"""True if a local Ollama server is reachable (or its binary is installed).
|
|
105
|
+
|
|
106
|
+
v3.8.2: used to RECOMMEND Mode B at setup with a single keypress. Bounded
|
|
107
|
+
and fail-safe — a slow/absent Ollama never blocks or slows the wizard.
|
|
108
|
+
"""
|
|
109
|
+
try:
|
|
110
|
+
import httpx
|
|
111
|
+
|
|
112
|
+
if httpx.get("http://localhost:11434/api/tags", timeout=1.5).status_code == 200:
|
|
113
|
+
return True
|
|
114
|
+
except Exception:
|
|
115
|
+
pass
|
|
116
|
+
try:
|
|
117
|
+
return shutil.which("ollama") is not None
|
|
118
|
+
except Exception:
|
|
119
|
+
return False
|
|
120
|
+
|
|
121
|
+
|
|
103
122
|
# ---------------------------------------------------------------------------
|
|
104
123
|
# Model download
|
|
105
124
|
# ---------------------------------------------------------------------------
|
|
@@ -388,14 +407,23 @@ def run_wizard(auto: bool = False) -> None:
|
|
|
388
407
|
return
|
|
389
408
|
|
|
390
409
|
# -- Step 2: Mode selection --
|
|
410
|
+
# v3.8.2: pre-detect Ollama so we can recommend Mode B with one keypress.
|
|
411
|
+
# No detection → zero-config Mode A. (Non-interactive always stays A: Mode B
|
|
412
|
+
# needs a pulled model, so we never assume it without the user confirming.)
|
|
413
|
+
ollama_present = _ollama_available()
|
|
414
|
+
default_mode = "b" if ollama_present else "a"
|
|
391
415
|
print()
|
|
392
416
|
print("─── Step 2/10: Choose Operating Mode ───")
|
|
393
417
|
print()
|
|
394
|
-
|
|
418
|
+
if ollama_present:
|
|
419
|
+
print(" ✓ Ollama detected on this machine — Mode B recommended.")
|
|
420
|
+
print(" Press Enter to accept, or pick another mode.")
|
|
421
|
+
print()
|
|
422
|
+
print(" [A] Local Guardian" + ("" if ollama_present else " (recommended)"))
|
|
395
423
|
print(" No model-provider call in the core memory path.")
|
|
396
424
|
print(" Review optional integrations and network policy for your deployment.")
|
|
397
425
|
print()
|
|
398
|
-
print(" [B] Smart Local")
|
|
426
|
+
print(" [B] Smart Local" + (" (recommended — Ollama detected)" if ollama_present else ""))
|
|
399
427
|
print(" Local LLM via Ollama for enrichment.")
|
|
400
428
|
print(" Data stays on your machine.")
|
|
401
429
|
print()
|
|
@@ -405,10 +433,13 @@ def run_wizard(auto: bool = False) -> None:
|
|
|
405
433
|
print()
|
|
406
434
|
|
|
407
435
|
if interactive:
|
|
408
|
-
choice = _prompt(
|
|
436
|
+
choice = _prompt(
|
|
437
|
+
f" Select mode [A/B/C] (default: {default_mode.upper()}): ",
|
|
438
|
+
default_mode,
|
|
439
|
+
).lower()
|
|
409
440
|
else:
|
|
410
441
|
choice = "a"
|
|
411
|
-
print(" Auto-selecting Mode A (non-interactive)")
|
|
442
|
+
print(" Auto-selecting Mode A (non-interactive, zero-config)")
|
|
412
443
|
|
|
413
444
|
if choice not in ("a", "b", "c"):
|
|
414
445
|
print(f" Invalid choice '{choice}', using Mode A.")
|
|
@@ -496,6 +527,15 @@ def run_wizard(auto: bool = False) -> None:
|
|
|
496
527
|
else:
|
|
497
528
|
print(f"\n ✓ CodeGraph disabled (enable later in {cg_config_path})")
|
|
498
529
|
|
|
530
|
+
# v3.8.2: up-front download summary so a large pull is never a surprise on
|
|
531
|
+
# a metered/slow link. Sizes approximate, one-time, cached locally.
|
|
532
|
+
if st_ok and not _embedding_is_remote(config):
|
|
533
|
+
print()
|
|
534
|
+
print(" Models to download (one-time, cached locally):")
|
|
535
|
+
print(" • Embedding model ~500 MB — required for semantic recall")
|
|
536
|
+
print(" • Reranker model ~130 MB — result quality")
|
|
537
|
+
print(" • Compression model ~560 MB — optional (you'll be asked)")
|
|
538
|
+
|
|
499
539
|
# -- Step 4: Download models --
|
|
500
540
|
print()
|
|
501
541
|
# H-06 (CVE-2025-14926): a malicious HuggingFace checkpoint can execute code
|
|
@@ -532,8 +572,25 @@ def run_wizard(auto: bool = False) -> None:
|
|
|
532
572
|
_download_reranker(_RERANKER_MODEL)
|
|
533
573
|
|
|
534
574
|
print()
|
|
535
|
-
print("─── Step 4c/10:
|
|
536
|
-
|
|
575
|
+
print("─── Step 4c/10: Compression Model (LLMLingua-2, ~560MB) ───")
|
|
576
|
+
print(" Only used for aggressive prompt compression. It also downloads")
|
|
577
|
+
print(" automatically the first time you enable that feature — so skipping")
|
|
578
|
+
print(" here is safe.")
|
|
579
|
+
# v3.8.2: consent-gate the 560MB compressor (was unconditional). Skip by
|
|
580
|
+
# default; it lazy-downloads on first real use of compression.
|
|
581
|
+
if not st_ok:
|
|
582
|
+
print(" ⚠ Skipped (sentence-transformers not installed)")
|
|
583
|
+
elif interactive:
|
|
584
|
+
comp_choice = _prompt(
|
|
585
|
+
" Download the ~560MB compression model now? [y/N] (default: N): ",
|
|
586
|
+
"n",
|
|
587
|
+
).lower()
|
|
588
|
+
if comp_choice in ("y", "yes"):
|
|
589
|
+
_download_compressor(_COMPRESSOR_MODEL)
|
|
590
|
+
else:
|
|
591
|
+
print(" ✓ Skipped — downloads automatically when you enable compression.")
|
|
592
|
+
else:
|
|
593
|
+
print(" ✓ Skipped (non-interactive) — downloads on first use.")
|
|
537
594
|
|
|
538
595
|
# -- Step 5: Daemon Configuration (v3.4.3) --
|
|
539
596
|
print()
|
|
@@ -655,17 +712,21 @@ def run_wizard(auto: bool = False) -> None:
|
|
|
655
712
|
print(" It detects degradation, generates improvements, and verifies them blindly.")
|
|
656
713
|
print(" Requires an LLM backend (Claude CLI, Ollama, or API key).")
|
|
657
714
|
print()
|
|
715
|
+
print(" OFF by default — when enabled it makes background LLM calls.")
|
|
658
716
|
print(" [Y] Enable Skill Evolution")
|
|
659
|
-
print(" [N]
|
|
717
|
+
print(" [N] Keep disabled (enable later: slm config set evolution.enabled true)")
|
|
660
718
|
print()
|
|
661
719
|
|
|
720
|
+
# v3.8.2: default OFF (was Y) — consistent with the npm profiles and no
|
|
721
|
+
# surprise outbound LLM calls for a fresh user. Opt-in, not opt-out.
|
|
662
722
|
if interactive:
|
|
663
|
-
evo_choice = _prompt(" Enable Skill Evolution? [
|
|
723
|
+
evo_choice = _prompt(" Enable Skill Evolution? [y/N] (default: N): ", "n").lower()
|
|
664
724
|
else:
|
|
665
|
-
evo_choice = "
|
|
666
|
-
print("
|
|
725
|
+
evo_choice = "n"
|
|
726
|
+
print(" Skill Evolution stays OFF (non-interactive) — enable later with:")
|
|
727
|
+
print(" slm config set evolution.enabled true")
|
|
667
728
|
|
|
668
|
-
evolution_enabled = evo_choice in ("
|
|
729
|
+
evolution_enabled = evo_choice in ("y", "yes")
|
|
669
730
|
|
|
670
731
|
# Write evolution config to config.json directly
|
|
671
732
|
# (SLMConfig.save() doesn't serialize evolution)
|
|
@@ -753,6 +814,21 @@ def run_wizard(auto: bool = False) -> None:
|
|
|
753
814
|
else:
|
|
754
815
|
print(" Adapters: none (enable via: slm adapters enable gmail)")
|
|
755
816
|
print()
|
|
817
|
+
# v3.8.2: surface component self-heal status so the user leaves setup
|
|
818
|
+
# KNOWING everything they need is present — or exactly what to run if not.
|
|
819
|
+
try:
|
|
820
|
+
from superlocalmemory.core import component_registry as _cr
|
|
821
|
+
|
|
822
|
+
_sum = _cr.snapshot(config)["summary"]
|
|
823
|
+
if _sum.get("healthy"):
|
|
824
|
+
print(" ✓ All components ready — models and dependencies present.")
|
|
825
|
+
else:
|
|
826
|
+
print(f" ⚠ {_sum.get('missing', 0)} component(s) need attention.")
|
|
827
|
+
print(" SLM auto-repairs fixable ones on daemon start. To check/fix now:")
|
|
828
|
+
print(" slm doctor (auto-fix: slm doctor --fix)")
|
|
829
|
+
except Exception:
|
|
830
|
+
pass
|
|
831
|
+
print()
|
|
756
832
|
print(" Quick start:")
|
|
757
833
|
print(' slm remember "your first memory"')
|
|
758
834
|
print(' slm recall "search query"')
|
|
@@ -760,8 +836,8 @@ def run_wizard(auto: bool = False) -> None:
|
|
|
760
836
|
print(" slm adapters enable gmail → start Gmail ingestion")
|
|
761
837
|
print()
|
|
762
838
|
print(" Need help?")
|
|
763
|
-
print(" slm
|
|
764
|
-
print(" slm
|
|
839
|
+
print(" slm help — every command, grouped (try: slm help self-heal)")
|
|
840
|
+
print(" slm doctor — diagnose issues (auto-fix: slm doctor --fix)")
|
|
765
841
|
print(" slm serve install — install auto-start service")
|
|
766
842
|
print(" https://github.com/qualixar/superlocalmemory")
|
|
767
843
|
print()
|
|
@@ -816,20 +892,70 @@ def check_first_use(command: str) -> None:
|
|
|
816
892
|
|
|
817
893
|
|
|
818
894
|
def _configure_external_integrations(*, interactive: bool) -> bool:
|
|
819
|
-
"""Request consent before editing Claude Code configuration.
|
|
895
|
+
"""Request consent before editing Claude Code configuration.
|
|
896
|
+
|
|
897
|
+
v3.8.2: after the Claude Code step, a pip user can also wire every OTHER
|
|
898
|
+
detected IDE in one step (previously only Claude Code was offered here;
|
|
899
|
+
other IDEs required a manual `slm connect <ide>` each).
|
|
900
|
+
"""
|
|
820
901
|
print()
|
|
821
902
|
print(" Optional Claude Code integration can install the SLM plugin and hooks.")
|
|
822
903
|
if not interactive:
|
|
823
904
|
print(" Skipped in non-interactive setup. Run: slm connect claude-code")
|
|
824
905
|
return False
|
|
906
|
+
changed = False
|
|
825
907
|
choice = _prompt(
|
|
826
908
|
" Install Claude Code plugin and hooks now? [y/N] (default: N): ",
|
|
827
909
|
"n",
|
|
828
910
|
).lower()
|
|
829
|
-
if choice
|
|
911
|
+
if choice in ("y", "yes"):
|
|
912
|
+
changed = _install_external_integrations()
|
|
913
|
+
else:
|
|
830
914
|
print(" Skipped. Run `slm connect claude-code` when ready.")
|
|
915
|
+
# v3.8.2: offer to connect all other detected IDEs (Cursor, VS Code, …).
|
|
916
|
+
changed = _configure_other_ides(interactive=interactive) or changed
|
|
917
|
+
return changed
|
|
918
|
+
|
|
919
|
+
|
|
920
|
+
def _configure_other_ides(*, interactive: bool) -> bool:
|
|
921
|
+
"""Consent-gated: connect all OTHER detected IDEs (Cursor, VS Code, …).
|
|
922
|
+
|
|
923
|
+
Uses the same IDEConnector the dashboard uses. Best-effort — a failure to
|
|
924
|
+
detect or connect any single IDE never blocks setup.
|
|
925
|
+
"""
|
|
926
|
+
if not interactive:
|
|
927
|
+
return False
|
|
928
|
+
try:
|
|
929
|
+
from superlocalmemory.hooks.ide_connector import IDEConnector
|
|
930
|
+
|
|
931
|
+
connector = IDEConnector()
|
|
932
|
+
installed = [s for s in connector.get_status() if s.get("installed")]
|
|
933
|
+
except Exception:
|
|
831
934
|
return False
|
|
832
|
-
|
|
935
|
+
others = [s for s in installed if s.get("id") not in ("claude-code", "claude")]
|
|
936
|
+
if not others:
|
|
937
|
+
return False
|
|
938
|
+
names = ", ".join(s.get("name", s.get("id")) for s in others)
|
|
939
|
+
print()
|
|
940
|
+
print(f" Detected other IDEs: {names}")
|
|
941
|
+
print(" SLM can wire them all so memory works across every editor.")
|
|
942
|
+
choice = _prompt(" Connect them now? [Y/n] (default: Y): ", "y").lower()
|
|
943
|
+
if choice not in ("", "y", "yes"):
|
|
944
|
+
print(" Skipped. Connect any later with: slm connect <ide>")
|
|
945
|
+
return False
|
|
946
|
+
changed = False
|
|
947
|
+
for s in others:
|
|
948
|
+
ide_id = s.get("id")
|
|
949
|
+
label = s.get("name", ide_id)
|
|
950
|
+
try:
|
|
951
|
+
if connector.connect(ide_id):
|
|
952
|
+
print(f" ✓ {label}")
|
|
953
|
+
changed = True
|
|
954
|
+
else:
|
|
955
|
+
print(f" ⚠ {label} — connect later: slm connect {ide_id}")
|
|
956
|
+
except Exception:
|
|
957
|
+
print(f" ⚠ {label} — connect later: slm connect {ide_id}")
|
|
958
|
+
return changed
|
|
833
959
|
|
|
834
960
|
|
|
835
961
|
def _install_external_integrations() -> bool:
|
|
@@ -26,6 +26,15 @@ _VERSION_PATTERN = re.compile(r"^[0-9A-Za-z.\-+_]{1,32}$")
|
|
|
26
26
|
|
|
27
27
|
_MAX_MARKER_BYTES = 64 # a semver string is ≤ 32 chars; 64 is plenty
|
|
28
28
|
|
|
29
|
+
_RELEASE_NOTES: dict[str, tuple[str, ...]] = {
|
|
30
|
+
"3.8.1": (
|
|
31
|
+
"Large existing databases no longer delay daemon readiness",
|
|
32
|
+
"Failed enrichment retries stop after ten automatic attempts",
|
|
33
|
+
"Dashboard panes stay mounted between navigation changes",
|
|
34
|
+
"Local model workers stay warm for a 30-minute working session",
|
|
35
|
+
),
|
|
36
|
+
}
|
|
37
|
+
|
|
29
38
|
|
|
30
39
|
def _data_dir() -> Path:
|
|
31
40
|
from superlocalmemory.infra.data_root import canonical_data_root
|
|
@@ -116,11 +125,16 @@ def _banner(prior: str | None, current: str) -> str:
|
|
|
116
125
|
header = (f"SuperLocalMemory upgraded from {prior} to {current}"
|
|
117
126
|
if prior else
|
|
118
127
|
f"SuperLocalMemory upgraded to {current} (from an earlier version)")
|
|
128
|
+
notes = _RELEASE_NOTES.get(
|
|
129
|
+
current,
|
|
130
|
+
(
|
|
131
|
+
"The installed version has changed; existing memory remains in place",
|
|
132
|
+
"See the release notes for version-specific changes",
|
|
133
|
+
),
|
|
134
|
+
)
|
|
119
135
|
return "\n".join([
|
|
120
136
|
header,
|
|
121
|
-
" -
|
|
122
|
-
" - Feedback and learning signals flow from every IDE to the daemon",
|
|
123
|
-
" - Silent data migration complete; no manual steps required",
|
|
137
|
+
*(f" - {note}" for note in notes),
|
|
124
138
|
"Run `slm doctor` to verify your setup.",
|
|
125
139
|
"",
|
|
126
140
|
])
|
|
@@ -52,7 +52,7 @@ class BackendOrchestrator:
|
|
|
52
52
|
"""Central coordinator for multi-backend architecture.
|
|
53
53
|
|
|
54
54
|
Lifecycle:
|
|
55
|
-
on_daemon_start() →
|
|
55
|
+
on_daemon_start() → initialize bounded backend state → ready
|
|
56
56
|
sync_new_fact() → called from store_pipeline after SQLite write
|
|
57
57
|
health_check() → returns status of all backends
|
|
58
58
|
"""
|
|
@@ -71,7 +71,7 @@ class BackendOrchestrator:
|
|
|
71
71
|
# ------------------------------------------------------------------
|
|
72
72
|
|
|
73
73
|
def on_daemon_start(self) -> None:
|
|
74
|
-
"""
|
|
74
|
+
"""Initialize bounded backend state without delaying daemon readiness."""
|
|
75
75
|
logger.info("BackendOrchestrator: daemon starting")
|
|
76
76
|
|
|
77
77
|
# 1. Apply schema (if not already applied)
|
|
@@ -82,18 +82,13 @@ class BackendOrchestrator:
|
|
|
82
82
|
try:
|
|
83
83
|
from superlocalmemory.core.tier_manager import evaluate_tiers
|
|
84
84
|
self._tiers = evaluate_tiers
|
|
85
|
-
logger.info("BackendOrchestrator:
|
|
85
|
+
logger.info("BackendOrchestrator: tier evaluator registered")
|
|
86
86
|
except Exception as exc:
|
|
87
87
|
logger.warning("TierManager init failed (non-fatal): %s", exc)
|
|
88
88
|
|
|
89
|
-
#
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
result = rebalance(self._db)
|
|
93
|
-
logger.info("BackendOrchestrator: initial rebalance — %s",
|
|
94
|
-
result.get("total_evaluated", "?"))
|
|
95
|
-
except Exception as exc:
|
|
96
|
-
logger.warning("Initial rebalance failed (non-fatal): %s", exc)
|
|
89
|
+
# Full-database tier evaluation belongs to MaintenanceScheduler. Running
|
|
90
|
+
# it here blocks FastAPI lifespan readiness on mature upgrade databases
|
|
91
|
+
# and occurs before optional projection backends are fully registered.
|
|
97
92
|
|
|
98
93
|
self._recover_interrupted_scale_promotion()
|
|
99
94
|
|
|
@@ -107,12 +102,12 @@ class BackendOrchestrator:
|
|
|
107
102
|
)
|
|
108
103
|
return
|
|
109
104
|
|
|
110
|
-
#
|
|
105
|
+
# 3. Initialize CozoDB if available
|
|
111
106
|
cozo_available = self._detect_cozo()
|
|
112
107
|
if cozo_available:
|
|
113
108
|
self._init_cozo()
|
|
114
109
|
|
|
115
|
-
#
|
|
110
|
+
# 4. Initialize LanceDB if available
|
|
116
111
|
lancedb_available = self._detect_lancedb()
|
|
117
112
|
if lancedb_available:
|
|
118
113
|
self._init_lancedb()
|
|
@@ -139,13 +134,19 @@ class BackendOrchestrator:
|
|
|
139
134
|
try:
|
|
140
135
|
from superlocalmemory.core.scale_engine import ScaleEngineManager
|
|
141
136
|
|
|
142
|
-
result = ScaleEngineManager(
|
|
137
|
+
result = ScaleEngineManager(
|
|
138
|
+
self._config,
|
|
139
|
+
profile_id="default",
|
|
140
|
+
).recover_interrupted_promotion()
|
|
143
141
|
if result:
|
|
144
142
|
logger.warning("Scale Engine promotion recovery: %s", result)
|
|
145
143
|
except Exception as exc:
|
|
146
144
|
# A scale projection is derived data. Startup must keep serving
|
|
147
145
|
# canonical SQLite even if optional recovery itself is unhealthy.
|
|
148
|
-
logger.error(
|
|
146
|
+
logger.error(
|
|
147
|
+
"Scale Engine recovery requires repair; Local Core remains active: %s",
|
|
148
|
+
exc,
|
|
149
|
+
)
|
|
149
150
|
|
|
150
151
|
# ------------------------------------------------------------------
|
|
151
152
|
# Incremental Sync (F-04: called from store_pipeline)
|
|
@@ -401,7 +402,8 @@ class BackendOrchestrator:
|
|
|
401
402
|
def _apply_schema_v345(self) -> None:
|
|
402
403
|
try:
|
|
403
404
|
from superlocalmemory.storage.schema_v345 import (
|
|
404
|
-
apply_migration,
|
|
405
|
+
apply_migration,
|
|
406
|
+
schema_version_applied,
|
|
405
407
|
)
|
|
406
408
|
# #47 fix: use raw_connection() — DatabaseManager has no `.conn`,
|
|
407
409
|
# so the old code raised AttributeError that was silently swallowed,
|