superlocalmemory 3.7.8 → 3.8.0
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/.claude-plugin/marketplace.json +1 -1
- package/ATTRIBUTION.md +1 -3
- package/CHANGELOG.md +69 -0
- package/README.md +199 -29
- package/package.json +4 -2
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin/CLAUDE.md +8 -8
- package/plugin/agents/slm-governance-advisor.md +80 -0
- package/plugin/agents/slm-loop-runner.md +71 -0
- package/plugin/agents/slm-memory-advisor.md +10 -5
- package/plugin/agents/slm-optimize-advisor.md +9 -3
- package/plugin/commands/slm-loop.md +31 -0
- package/plugin/hooks/hooks.json +79 -0
- package/plugin/requirements.txt +1 -1
- package/plugin/scripts/slm-launch +46 -7
- package/plugin/settings.json +9 -0
- package/plugin/skills/slm-cache/SKILL.md +9 -1
- package/plugin/skills/slm-compress/SKILL.md +8 -1
- package/plugin/skills/slm-governance/SKILL.md +248 -0
- package/plugin/skills/slm-graph/SKILL.md +17 -3
- package/plugin/skills/slm-loop/SKILL.md +99 -0
- package/plugin/skills/slm-mesh/SKILL.md +282 -0
- package/plugin/skills/slm-profile/SKILL.md +148 -0
- package/plugin/skills/slm-recall/SKILL.md +46 -10
- package/plugin/skills/slm-remember/SKILL.md +48 -1
- package/plugin/skills/slm-scope/SKILL.md +176 -0
- package/plugin/skills/slm-session/SKILL.md +24 -1
- package/plugin/skills/slm-status/SKILL.md +18 -1
- package/plugin-src/agents/slm-governance-advisor.md +80 -0
- package/plugin-src/agents/slm-loop-runner.md +71 -0
- package/plugin-src/agents/slm-memory-advisor.md +10 -5
- package/plugin-src/agents/slm-optimize-advisor.md +9 -3
- package/plugin-src/commands/slm-loop.md +31 -0
- package/plugin-src/hooks/hooks.json +79 -0
- package/plugin-src/manifest.json +7 -2
- package/plugin-src/requirements.txt +1 -1
- package/plugin-src/rules/AGENTS.md +57 -18
- package/plugin-src/rules/CLAUDE.md.fragment +8 -8
- package/plugin-src/scripts/slm-launch +46 -7
- package/plugin-src/settings.json +9 -0
- package/plugin-src/skills/slm-cache/SKILL.md +9 -1
- package/plugin-src/skills/slm-compress/SKILL.md +8 -1
- package/plugin-src/skills/slm-governance/SKILL.md +248 -0
- package/plugin-src/skills/slm-graph/SKILL.md +17 -3
- package/plugin-src/skills/slm-loop/SKILL.md +99 -0
- package/plugin-src/skills/slm-mesh/SKILL.md +282 -0
- package/plugin-src/skills/slm-profile/SKILL.md +148 -0
- package/plugin-src/skills/slm-recall/SKILL.md +46 -10
- package/plugin-src/skills/slm-remember/SKILL.md +48 -1
- package/plugin-src/skills/slm-scope/SKILL.md +176 -0
- package/plugin-src/skills/slm-session/SKILL.md +24 -1
- package/plugin-src/skills/slm-status/SKILL.md +18 -1
- package/pyproject.toml +1 -1
- package/scripts/postinstall/validation.js +2 -0
- package/scripts/postinstall-interactive.js +74 -2
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/access/__init__.py +3 -0
- package/src/superlocalmemory/access/rbac.py +477 -0
- package/src/superlocalmemory/cli/commands.py +94 -10
- package/src/superlocalmemory/cli/compress_cmd.py +17 -7
- package/src/superlocalmemory/cli/loop_cmd.py +192 -0
- package/src/superlocalmemory/cli/main.py +39 -4
- package/src/superlocalmemory/cli/mesh_cmd.py +38 -0
- package/src/superlocalmemory/cli/optimize_cmd.py +3 -0
- package/src/superlocalmemory/cli/pending_store.py +49 -13
- package/src/superlocalmemory/cli/proxy_cmd.py +4 -0
- package/src/superlocalmemory/cli/scale_engine_cmd.py +6 -0
- package/src/superlocalmemory/cli/setup_wizard.py +22 -13
- package/src/superlocalmemory/compliance/audit.py +6 -0
- package/src/superlocalmemory/compliance/gdpr.py +128 -138
- package/src/superlocalmemory/compliance/retention.py +176 -45
- package/src/superlocalmemory/core/backend_orchestrator.py +5 -43
- package/src/superlocalmemory/core/community_summary.py +267 -0
- package/src/superlocalmemory/core/config.py +216 -3
- package/src/superlocalmemory/core/consolidation_engine.py +95 -22
- package/src/superlocalmemory/core/context_cache.py +61 -18
- package/src/superlocalmemory/core/embedding_worker.py +17 -2
- package/src/superlocalmemory/core/embeddings.py +12 -1
- package/src/superlocalmemory/core/engine.py +17 -1
- package/src/superlocalmemory/core/engine_ingestion.py +29 -0
- package/src/superlocalmemory/core/engine_wiring.py +13 -0
- package/src/superlocalmemory/core/entity_community.py +178 -0
- package/src/superlocalmemory/core/graph_analyzer.py +39 -2
- package/src/superlocalmemory/core/graph_pruner.py +13 -8
- package/src/superlocalmemory/core/key_expander.py +138 -0
- package/src/superlocalmemory/core/maintenance.py +23 -0
- package/src/superlocalmemory/core/modes.py +1 -1
- package/src/superlocalmemory/core/mutations.py +2 -2
- package/src/superlocalmemory/core/pii.py +105 -0
- package/src/superlocalmemory/core/progressive_abstraction.py +208 -0
- package/src/superlocalmemory/core/recall_pipeline.py +2 -0
- package/src/superlocalmemory/core/recall_worker.py +20 -6
- package/src/superlocalmemory/core/scale_engine.py +60 -1
- package/src/superlocalmemory/core/security_primitives.py +40 -2
- package/src/superlocalmemory/core/store_pipeline.py +35 -11
- package/src/superlocalmemory/core/worker_pool.py +21 -6
- package/src/superlocalmemory/encoding/entity_reflexion.py +200 -0
- package/src/superlocalmemory/encoding/entity_resolver.py +34 -24
- package/src/superlocalmemory/encoding/fact_extractor.py +26 -1
- package/src/superlocalmemory/encoding/temporal_validator.py +64 -1
- package/src/superlocalmemory/evolution/evolution_store.py +122 -45
- package/src/superlocalmemory/evolution/llm_dispatch.py +12 -1
- package/src/superlocalmemory/evolution/model_selection.py +160 -0
- package/src/superlocalmemory/evolution/mutation_generator.py +16 -0
- package/src/superlocalmemory/evolution/skill_evolver.py +127 -42
- package/src/superlocalmemory/evolution/triggers.py +22 -13
- package/src/superlocalmemory/graph/cozo_backend.py +43 -20
- package/src/superlocalmemory/hooks/adapter_base.py +5 -1
- package/src/superlocalmemory/hooks/auto_recall.py +13 -1
- package/src/superlocalmemory/hooks/claude_code_hooks.py +11 -0
- package/src/superlocalmemory/hooks/codex_assets.py +64 -5
- package/src/superlocalmemory/hooks/hook_daemon.py +20 -3
- package/src/superlocalmemory/hooks/memory_protocol.py +54 -0
- package/src/superlocalmemory/hooks/portable_kit.py +114 -1
- package/src/superlocalmemory/infra/backup.py +12 -1
- package/src/superlocalmemory/infra/daemon_identity.py +40 -4
- package/src/superlocalmemory/infra/data_root.py +43 -4
- package/src/superlocalmemory/infra/event_bus.py +107 -24
- package/src/superlocalmemory/infra/rate_limiter.py +93 -0
- package/src/superlocalmemory/ingestion/adapter_manager.py +4 -1
- package/src/superlocalmemory/ingestion/credentials.py +1 -1
- package/src/superlocalmemory/learning/cross_project.py +28 -19
- package/src/superlocalmemory/learning/reward_proxy.py +42 -9
- package/src/superlocalmemory/loops/__init__.py +56 -0
- package/src/superlocalmemory/loops/budget.py +58 -0
- package/src/superlocalmemory/loops/engine.py +164 -0
- package/src/superlocalmemory/loops/ledger.py +243 -0
- package/src/superlocalmemory/loops/models.py +152 -0
- package/src/superlocalmemory/loops/rules.py +52 -0
- package/src/superlocalmemory/mcp/_daemon_proxy.py +3 -0
- package/src/superlocalmemory/mcp/_pool_adapter.py +2 -0
- package/src/superlocalmemory/mcp/profiles.py +103 -0
- package/src/superlocalmemory/mcp/server.py +21 -49
- package/src/superlocalmemory/mcp/tools_active.py +4 -7
- package/src/superlocalmemory/mcp/tools_code_graph.py +51 -5
- package/src/superlocalmemory/mcp/tools_core.py +8 -1
- package/src/superlocalmemory/mcp/tools_evolution.py +6 -3
- package/src/superlocalmemory/mcp/tools_loops.py +300 -0
- package/src/superlocalmemory/mcp/tools_mesh.py +140 -4
- package/src/superlocalmemory/mcp/tools_optimize.py +15 -8
- package/src/superlocalmemory/mesh/broker.py +237 -129
- package/src/superlocalmemory/mesh/remote_sync.py +50 -8
- package/src/superlocalmemory/optimize/NOTICE +1 -6
- package/src/superlocalmemory/optimize/adapters/anthropic_adapter.py +1 -4
- package/src/superlocalmemory/optimize/adapters/openai_adapter.py +1 -4
- package/src/superlocalmemory/optimize/cache/semantic.py +27 -19
- package/src/superlocalmemory/optimize/compress/align.py +32 -26
- package/src/superlocalmemory/optimize/compress/ccr.py +14 -71
- package/src/superlocalmemory/optimize/compress/router.py +105 -22
- package/src/superlocalmemory/optimize/config/defaults.py +1 -1
- package/src/superlocalmemory/optimize/config/schema.py +87 -4
- package/src/superlocalmemory/optimize/metrics/counters.py +13 -4
- package/src/superlocalmemory/optimize/metrics/estimator.py +0 -3
- package/src/superlocalmemory/optimize/proxy/_helpers.py +31 -4
- package/src/superlocalmemory/optimize/storage/db.py +38 -9
- package/src/superlocalmemory/optimize/storage/schema.py +10 -0
- package/src/superlocalmemory/parameterization/pattern_extractor.py +6 -3
- package/src/superlocalmemory/retrieval/agentic.py +1 -1
- package/src/superlocalmemory/retrieval/bm25_channel.py +68 -10
- package/src/superlocalmemory/retrieval/engine.py +168 -26
- package/src/superlocalmemory/retrieval/entity_channel.py +7 -5
- package/src/superlocalmemory/retrieval/hopfield_channel.py +9 -2
- package/src/superlocalmemory/retrieval/semantic_channel.py +114 -21
- package/src/superlocalmemory/retrieval/spreading_activation.py +11 -2
- package/src/superlocalmemory/retrieval/temporal_channel.py +48 -9
- package/src/superlocalmemory/retrieval/temporal_frame.py +102 -0
- package/src/superlocalmemory/retrieval/temporal_validity_filter.py +135 -0
- package/src/superlocalmemory/retrieval/time_window.py +181 -0
- package/src/superlocalmemory/server/api.py +4 -4
- package/src/superlocalmemory/server/profile_runtime.py +125 -8
- package/src/superlocalmemory/server/rbac_enforce.py +142 -0
- package/src/superlocalmemory/server/recall_health.py +24 -3
- package/src/superlocalmemory/server/recall_serializer.py +19 -1
- package/src/superlocalmemory/server/routes/abstraction.py +115 -0
- package/src/superlocalmemory/server/routes/agents.py +128 -38
- package/src/superlocalmemory/server/routes/backup.py +34 -10
- package/src/superlocalmemory/server/routes/behavioral.py +13 -12
- package/src/superlocalmemory/server/routes/brain.py +21 -5
- package/src/superlocalmemory/server/routes/chat.py +10 -5
- package/src/superlocalmemory/server/routes/compliance.py +171 -21
- package/src/superlocalmemory/server/routes/config_api.py +436 -0
- package/src/superlocalmemory/server/routes/data_io.py +30 -8
- package/src/superlocalmemory/server/routes/entity.py +9 -4
- package/src/superlocalmemory/server/routes/events.py +24 -8
- package/src/superlocalmemory/server/routes/evolution.py +135 -17
- package/src/superlocalmemory/server/routes/helpers.py +16 -1
- package/src/superlocalmemory/server/routes/ingest.py +7 -4
- package/src/superlocalmemory/server/routes/insights.py +3 -3
- package/src/superlocalmemory/server/routes/learning.py +14 -14
- package/src/superlocalmemory/server/routes/lifecycle.py +59 -8
- package/src/superlocalmemory/server/routes/memories.py +182 -57
- package/src/superlocalmemory/server/routes/mesh.py +95 -15
- package/src/superlocalmemory/server/routes/optimize.py +33 -1
- package/src/superlocalmemory/server/routes/prewarm.py +2 -0
- package/src/superlocalmemory/server/routes/profiles.py +63 -17
- package/src/superlocalmemory/server/routes/ratelimit.py +124 -0
- package/src/superlocalmemory/server/routes/rbac.py +367 -0
- package/src/superlocalmemory/server/routes/stats.py +13 -6
- package/src/superlocalmemory/server/routes/tiers.py +11 -9
- package/src/superlocalmemory/server/routes/v3_api.py +183 -69
- package/src/superlocalmemory/server/routes/ws.py +5 -2
- package/src/superlocalmemory/server/security_middleware.py +12 -5
- package/src/superlocalmemory/server/ui.py +20 -5
- package/src/superlocalmemory/server/unified_daemon.py +384 -56
- package/src/superlocalmemory/server/write_identity.py +38 -8
- package/src/superlocalmemory/storage/database.py +265 -53
- package/src/superlocalmemory/storage/migration_runner.py +53 -0
- package/src/superlocalmemory/storage/migrations/M021_ingestion_log_profile.py +108 -0
- package/src/superlocalmemory/storage/migrations/M022_entity_aliases_profile.py +86 -0
- package/src/superlocalmemory/storage/migrations/M023_mesh_profile_isolation.py +194 -0
- package/src/superlocalmemory/storage/migrations/M024_rbac_users_roles.py +87 -0
- package/src/superlocalmemory/storage/migrations/M025_perf_indexes.py +90 -0
- package/src/superlocalmemory/storage/migrations/M026_rbac_memberships_fk.py +136 -0
- package/src/superlocalmemory/storage/migrations/M027_transferable_patterns_profile.py +163 -0
- package/src/superlocalmemory/storage/models.py +4 -0
- package/src/superlocalmemory/storage/schema.py +87 -0
- package/src/superlocalmemory/storage/schema_v343.py +24 -12
- package/src/superlocalmemory/trust/gate.py +49 -8
- package/src/superlocalmemory/ui/assets/slm-icon-white.svg +64 -0
- package/src/superlocalmemory/ui/assets/slm-icon.svg +36 -0
- package/src/superlocalmemory/ui/css/design-system.css +621 -0
- package/src/superlocalmemory/ui/css/neural-glass.css +6 -0
- package/src/superlocalmemory/ui/css/od-bridge.css +158 -0
- package/src/superlocalmemory/ui/favicon.svg +35 -4
- package/src/superlocalmemory/ui/index.html +306 -173
- package/src/superlocalmemory/ui/js/brain.js +5 -20
- package/src/superlocalmemory/ui/js/core.js +47 -31
- package/src/superlocalmemory/ui/js/dashboard.js +314 -63
- package/src/superlocalmemory/ui/js/event-delegation.js +102 -0
- package/src/superlocalmemory/ui/js/knowledge-graph.js +11 -11
- package/src/superlocalmemory/ui/js/math-health.js +1 -1
- package/src/superlocalmemory/ui/js/memories.js +15 -4
- package/src/superlocalmemory/ui/js/memory-chat.js +7 -7
- package/src/superlocalmemory/ui/js/ng-entities.js +6 -8
- package/src/superlocalmemory/ui/js/ng-ingestion.js +4 -4
- package/src/superlocalmemory/ui/js/ng-mesh.js +4 -9
- package/src/superlocalmemory/ui/js/ng-shell.js +8 -8
- package/src/superlocalmemory/ui/js/ng-skills.js +54 -2
- package/src/superlocalmemory/ui/js/od-agents.js +544 -0
- package/src/superlocalmemory/ui/js/od-auth-gate.js +257 -0
- package/src/superlocalmemory/ui/js/od-backup.js +780 -0
- package/src/superlocalmemory/ui/js/od-brain.js +779 -0
- package/src/superlocalmemory/ui/js/od-entities.js +579 -0
- package/src/superlocalmemory/ui/js/od-graph.js +593 -0
- package/src/superlocalmemory/ui/js/od-health.js +539 -0
- package/src/superlocalmemory/ui/js/od-mcp.js +508 -0
- package/src/superlocalmemory/ui/js/od-memories.js +887 -0
- package/src/superlocalmemory/ui/js/od-mesh.js +539 -0
- package/src/superlocalmemory/ui/js/od-operations.js +1250 -0
- package/src/superlocalmemory/ui/js/od-optimize.js +787 -0
- package/src/superlocalmemory/ui/js/od-settings.js +1053 -0
- package/src/superlocalmemory/ui/js/od-shell.js +593 -0
- package/src/superlocalmemory/ui/js/od-skills.js +573 -0
- package/src/superlocalmemory/ui/js/od-team.js +258 -0
- package/src/superlocalmemory/ui/js/profiles.js +159 -46
- package/src/superlocalmemory/ui/js/settings.js +2 -2
- package/src/superlocalmemory/ui/js/timeline.js +34 -5
- package/src/superlocalmemory/ui/js/trust-dashboard.js +2 -2
- package/src/superlocalmemory/vector/lancedb_backend.py +8 -6
- package/src/superlocalmemory/learning/behavioral_listener.py +0 -94
|
@@ -19,6 +19,18 @@ logger = logging.getLogger(__name__)
|
|
|
19
19
|
router = APIRouter(prefix="/api/v3", tags=["v3"])
|
|
20
20
|
|
|
21
21
|
|
|
22
|
+
def _internal_error() -> JSONResponse:
|
|
23
|
+
"""SEC-H-02: log the full traceback server-side, return a generic message.
|
|
24
|
+
|
|
25
|
+
Returning ``str(e)`` to the client leaked DB schema (column/constraint
|
|
26
|
+
names), the data-directory filesystem path, and — for config routes — could
|
|
27
|
+
surface LLM config internals. Call this only from inside an ``except`` block
|
|
28
|
+
so ``logger.exception`` captures the active traceback.
|
|
29
|
+
"""
|
|
30
|
+
logger.exception("v3_api request failed")
|
|
31
|
+
return JSONResponse({"error": "Internal server error"}, status_code=500)
|
|
32
|
+
|
|
33
|
+
|
|
22
34
|
async def _apply_runtime_config(request: Request, config, *, mode_change: bool) -> None:
|
|
23
35
|
"""Persist and hot-swap config only after the daemon transition succeeds."""
|
|
24
36
|
import asyncio
|
|
@@ -99,7 +111,7 @@ async def dashboard(request: Request):
|
|
|
99
111
|
"version": SLM_VERSION,
|
|
100
112
|
}
|
|
101
113
|
except Exception as e:
|
|
102
|
-
return
|
|
114
|
+
return _internal_error()
|
|
103
115
|
|
|
104
116
|
|
|
105
117
|
# ── Mode ─────────────────────────────────────────────────────
|
|
@@ -109,26 +121,36 @@ async def get_mode(request: Request):
|
|
|
109
121
|
"""Get current mode, provider, model — single source of truth for UI."""
|
|
110
122
|
try:
|
|
111
123
|
from superlocalmemory.core.config import SLMConfig
|
|
124
|
+
from urllib.parse import urlparse
|
|
112
125
|
config = getattr(request.app.state, "config", None) or SLMConfig.load()
|
|
113
126
|
current = config.mode.value
|
|
127
|
+
# SEC-L-01: expose only the endpoint HOST, not the full URL. A full
|
|
128
|
+
# api_base (e.g. an internal Azure OpenAI deployment path) leaks
|
|
129
|
+
# infrastructure topology to non-admin (viewer) users.
|
|
130
|
+
_api_base = config.llm.api_base or ""
|
|
131
|
+
_endpoint_host = urlparse(_api_base).netloc if _api_base else ""
|
|
114
132
|
return {
|
|
115
133
|
"mode": current,
|
|
116
134
|
"provider": config.llm.provider or "none",
|
|
117
135
|
"model": config.llm.model or "",
|
|
118
136
|
"has_key": bool(config.llm.api_key),
|
|
119
|
-
"endpoint":
|
|
137
|
+
"endpoint": _endpoint_host,
|
|
120
138
|
"capabilities": {
|
|
121
139
|
"llm_available": bool(config.llm.provider),
|
|
122
140
|
"cross_encoder": config.retrieval.use_cross_encoder if hasattr(config, 'retrieval') else False,
|
|
123
141
|
},
|
|
124
142
|
}
|
|
125
143
|
except Exception as e:
|
|
126
|
-
return
|
|
144
|
+
return _internal_error()
|
|
127
145
|
|
|
128
146
|
|
|
129
147
|
@router.put("/mode")
|
|
130
148
|
async def set_mode(request: Request):
|
|
131
149
|
"""Switch operating mode. Body: {"mode": "a"|"b"|"c"}"""
|
|
150
|
+
# SEC-H-01: mode switch reconfigures the whole engine — admin-only (MANAGE)
|
|
151
|
+
# on top of machine auth. Owner keeps MANAGE; personal mode unaffected.
|
|
152
|
+
from superlocalmemory.server.rbac_enforce import require_manage
|
|
153
|
+
require_manage(request)
|
|
132
154
|
try:
|
|
133
155
|
body = await request.json()
|
|
134
156
|
new_mode = body.get("mode", "").lower()
|
|
@@ -195,7 +217,7 @@ async def set_mode(request: Request):
|
|
|
195
217
|
"message": "Embedding re-indexing will run on next recall." if needs_reindex else "",
|
|
196
218
|
}
|
|
197
219
|
except Exception as e:
|
|
198
|
-
return
|
|
220
|
+
return _internal_error()
|
|
199
221
|
|
|
200
222
|
|
|
201
223
|
@router.post("/mode/set")
|
|
@@ -204,6 +226,9 @@ async def set_full_config(request: Request):
|
|
|
204
226
|
|
|
205
227
|
V3.4.24: Also accepts embedding_* fields for custom embedding endpoints.
|
|
206
228
|
"""
|
|
229
|
+
# SEC-H-01: sets the LLM provider + API key for the whole engine — admin-only.
|
|
230
|
+
from superlocalmemory.server.rbac_enforce import require_manage
|
|
231
|
+
require_manage(request)
|
|
207
232
|
try:
|
|
208
233
|
body = await request.json()
|
|
209
234
|
new_mode = body.get("mode", "a").lower()
|
|
@@ -287,7 +312,7 @@ async def set_full_config(request: Request):
|
|
|
287
312
|
"embedding_dimension": config.embedding.dimension,
|
|
288
313
|
}
|
|
289
314
|
except Exception as e:
|
|
290
|
-
return
|
|
315
|
+
return _internal_error()
|
|
291
316
|
|
|
292
317
|
|
|
293
318
|
# ── V3.4.24: Embedding Configuration ────────────────────────────────
|
|
@@ -309,12 +334,15 @@ async def get_embedding_config(request: Request):
|
|
|
309
334
|
"mode": config.mode.value,
|
|
310
335
|
}
|
|
311
336
|
except Exception as e:
|
|
312
|
-
return
|
|
337
|
+
return _internal_error()
|
|
313
338
|
|
|
314
339
|
|
|
315
340
|
@router.put("/embedding/config")
|
|
316
341
|
async def set_embedding_config(request: Request):
|
|
317
342
|
"""Update embedding configuration independently of mode switch."""
|
|
343
|
+
# SEC-H-01: swapping the embedding provider/key affects all recall — admin-only.
|
|
344
|
+
from superlocalmemory.server.rbac_enforce import require_manage
|
|
345
|
+
require_manage(request)
|
|
318
346
|
try:
|
|
319
347
|
body = await request.json()
|
|
320
348
|
from superlocalmemory.core.config import SLMConfig, EmbeddingConfig
|
|
@@ -362,7 +390,7 @@ async def set_embedding_config(request: Request):
|
|
|
362
390
|
"needs_reindex": needs_reindex,
|
|
363
391
|
}
|
|
364
392
|
except Exception as e:
|
|
365
|
-
return
|
|
393
|
+
return _internal_error()
|
|
366
394
|
|
|
367
395
|
|
|
368
396
|
@router.get("/scope/config")
|
|
@@ -374,12 +402,15 @@ async def get_scope_config(request: Request):
|
|
|
374
402
|
config = getattr(request.app.state, "config", None) or SLMConfig.load()
|
|
375
403
|
return {"success": True, **config.scope.as_dict()}
|
|
376
404
|
except Exception as exc:
|
|
377
|
-
return
|
|
405
|
+
return _internal_error()
|
|
378
406
|
|
|
379
407
|
|
|
380
408
|
@router.put("/scope/config")
|
|
381
409
|
async def set_scope_config(request: Request):
|
|
382
410
|
"""Validate, persist, and hot-apply explicit multi-scope defaults."""
|
|
411
|
+
# SEC-H-01: scope defaults govern cross-profile recall visibility — admin-only.
|
|
412
|
+
from superlocalmemory.server.rbac_enforce import require_manage
|
|
413
|
+
require_manage(request)
|
|
383
414
|
try:
|
|
384
415
|
body = await request.json()
|
|
385
416
|
from superlocalmemory.core.config import SLMConfig, ScopeConfig
|
|
@@ -411,7 +442,7 @@ async def set_scope_config(request: Request):
|
|
|
411
442
|
await _apply_runtime_config(request, config, mode_change=False)
|
|
412
443
|
return {"success": True, **config.scope.as_dict()}
|
|
413
444
|
except Exception as exc:
|
|
414
|
-
return
|
|
445
|
+
return _internal_error()
|
|
415
446
|
|
|
416
447
|
|
|
417
448
|
@router.post("/embedding/test")
|
|
@@ -475,8 +506,9 @@ async def embed_ping():
|
|
|
475
506
|
from .helpers import get_engine_lazy
|
|
476
507
|
# We just need to confirm the route is alive — engine check is optional
|
|
477
508
|
return {"ok": True}
|
|
478
|
-
except Exception
|
|
479
|
-
|
|
509
|
+
except Exception:
|
|
510
|
+
logger.exception("embedder liveness probe failed")
|
|
511
|
+
return JSONResponse({"ok": False, "error": "Internal server error"}, status_code=503)
|
|
480
512
|
|
|
481
513
|
|
|
482
514
|
@router.post("/embed")
|
|
@@ -509,7 +541,7 @@ async def embed_texts(request: Request):
|
|
|
509
541
|
)
|
|
510
542
|
return {"embeddings": embeddings}
|
|
511
543
|
except Exception as e:
|
|
512
|
-
return
|
|
544
|
+
return _internal_error()
|
|
513
545
|
|
|
514
546
|
|
|
515
547
|
def _validate_provider_url(url: str, client_host: str) -> str | None:
|
|
@@ -636,7 +668,8 @@ async def test_provider(request: Request):
|
|
|
636
668
|
except httpx.HTTPStatusError as e:
|
|
637
669
|
return {"success": False, "error": f"HTTP {e.response.status_code}: Invalid key or endpoint"}
|
|
638
670
|
except Exception as e:
|
|
639
|
-
|
|
671
|
+
logger.exception("verify_key failed")
|
|
672
|
+
return {"success": False, "error": "internal error"}
|
|
640
673
|
|
|
641
674
|
|
|
642
675
|
@router.get("/ollama/status")
|
|
@@ -666,7 +699,8 @@ async def list_providers():
|
|
|
666
699
|
from superlocalmemory.core.config import SLMConfig
|
|
667
700
|
return {"providers": SLMConfig.provider_presets()}
|
|
668
701
|
except Exception as exc:
|
|
669
|
-
|
|
702
|
+
logger.exception("list_providers failed")
|
|
703
|
+
return {"error": "could not load provider list", "providers": []}
|
|
670
704
|
|
|
671
705
|
|
|
672
706
|
@router.get("/provider")
|
|
@@ -685,12 +719,20 @@ async def get_provider():
|
|
|
685
719
|
"has_key": bool(key),
|
|
686
720
|
}
|
|
687
721
|
except Exception as exc:
|
|
688
|
-
|
|
722
|
+
logger.exception("get_provider failed")
|
|
723
|
+
return {"error": "could not load configuration", "provider": "unknown"}
|
|
689
724
|
|
|
690
725
|
|
|
691
726
|
@router.put("/provider")
|
|
692
727
|
async def set_provider(request: Request):
|
|
693
728
|
"""Set LLM provider. Body: {"provider": "openai", "api_key": "...", "model": "..."}"""
|
|
729
|
+
# Swapping the provider + API key redirects all LLM traffic (prompt/output
|
|
730
|
+
# interception risk) — admin-only, on top of mutation auth.
|
|
731
|
+
from superlocalmemory.server.route_mutations import authorize_route_mutation
|
|
732
|
+
from superlocalmemory.server.rbac_enforce import require_manage
|
|
733
|
+
_auth = authorize_route_mutation(request, operation="update",
|
|
734
|
+
source_agent_id="http-set-provider")
|
|
735
|
+
require_manage(request)
|
|
694
736
|
try:
|
|
695
737
|
body = await request.json()
|
|
696
738
|
provider = body.get("provider", "")
|
|
@@ -720,7 +762,7 @@ async def set_provider(request: Request):
|
|
|
720
762
|
|
|
721
763
|
return {"success": True, "provider": provider, "model": model}
|
|
722
764
|
except Exception as e:
|
|
723
|
-
return
|
|
765
|
+
return _internal_error()
|
|
724
766
|
|
|
725
767
|
|
|
726
768
|
# ── Recall Trace ─────────────────────────────────────────────
|
|
@@ -739,6 +781,7 @@ async def recall_trace(request: Request):
|
|
|
739
781
|
body = await request.json()
|
|
740
782
|
query = body.get("query", "")
|
|
741
783
|
limit = body.get("limit", 10)
|
|
784
|
+
window = body.get("window", "") or ""
|
|
742
785
|
|
|
743
786
|
# Use daemon engine — already loaded, shares warm page cache.
|
|
744
787
|
# run_in_executor keeps event loop alive so browser doesn't abort.
|
|
@@ -751,7 +794,7 @@ async def recall_trace(request: Request):
|
|
|
751
794
|
t0 = _time.monotonic()
|
|
752
795
|
response = await loop.run_in_executor(
|
|
753
796
|
None,
|
|
754
|
-
lambda: engine.recall(query, limit=limit, fast=False),
|
|
797
|
+
lambda: engine.recall(query, limit=limit, fast=False, window=window or None),
|
|
755
798
|
)
|
|
756
799
|
elapsed_ms = round((_time.monotonic() - t0) * 1000, 1)
|
|
757
800
|
|
|
@@ -784,7 +827,7 @@ async def recall_trace(request: Request):
|
|
|
784
827
|
**recall_response_metadata(response),
|
|
785
828
|
}
|
|
786
829
|
except Exception as e:
|
|
787
|
-
return
|
|
830
|
+
return _internal_error()
|
|
788
831
|
|
|
789
832
|
|
|
790
833
|
def _record_learning_signals(query: str, results: list) -> None:
|
|
@@ -874,7 +917,7 @@ async def trust_dashboard(request: Request):
|
|
|
874
917
|
"profile": pid,
|
|
875
918
|
}
|
|
876
919
|
except Exception as e:
|
|
877
|
-
return
|
|
920
|
+
return _internal_error()
|
|
878
921
|
|
|
879
922
|
|
|
880
923
|
# ── Math Health ──────────────────────────────────────────────
|
|
@@ -916,7 +959,7 @@ async def math_health(request: Request):
|
|
|
916
959
|
"note": "config-derived; not a live runtime probe",
|
|
917
960
|
}
|
|
918
961
|
except Exception as e:
|
|
919
|
-
return
|
|
962
|
+
return _internal_error()
|
|
920
963
|
|
|
921
964
|
|
|
922
965
|
# ── Auto-Capture / Auto-Recall Config ────────────────────────
|
|
@@ -930,7 +973,8 @@ async def get_auto_capture_config():
|
|
|
930
973
|
rules = RulesEngine(config_path=DEFAULT_BASE_DIR / "config.json")
|
|
931
974
|
return {"config": rules.get_capture_config()}
|
|
932
975
|
except Exception as exc:
|
|
933
|
-
|
|
976
|
+
logger.exception("get_auto_capture_config failed")
|
|
977
|
+
return {"error": "internal error", "config": {}}
|
|
934
978
|
|
|
935
979
|
|
|
936
980
|
@router.put("/auto-capture/config")
|
|
@@ -947,7 +991,7 @@ async def set_auto_capture_config(request: Request):
|
|
|
947
991
|
rules.save(config_path)
|
|
948
992
|
return {"success": True, "config": rules.get_capture_config()}
|
|
949
993
|
except Exception as e:
|
|
950
|
-
return
|
|
994
|
+
return _internal_error()
|
|
951
995
|
|
|
952
996
|
|
|
953
997
|
@router.get("/auto-recall/config")
|
|
@@ -959,7 +1003,8 @@ async def get_auto_recall_config():
|
|
|
959
1003
|
rules = RulesEngine(config_path=DEFAULT_BASE_DIR / "config.json")
|
|
960
1004
|
return {"config": rules.get_recall_config()}
|
|
961
1005
|
except Exception as exc:
|
|
962
|
-
|
|
1006
|
+
logger.exception("get_auto_recall_config failed")
|
|
1007
|
+
return {"error": "internal error", "config": {}}
|
|
963
1008
|
|
|
964
1009
|
|
|
965
1010
|
@router.put("/auto-recall/config")
|
|
@@ -976,7 +1021,7 @@ async def set_auto_recall_config(request: Request):
|
|
|
976
1021
|
rules.save(config_path)
|
|
977
1022
|
return {"success": True, "config": rules.get_recall_config()}
|
|
978
1023
|
except Exception as e:
|
|
979
|
-
return
|
|
1024
|
+
return _internal_error()
|
|
980
1025
|
|
|
981
1026
|
|
|
982
1027
|
# ── IDE Status ───────────────────────────────────────────────
|
|
@@ -989,7 +1034,8 @@ async def ide_status():
|
|
|
989
1034
|
connector = IDEConnector()
|
|
990
1035
|
return {"ides": connector.get_status()}
|
|
991
1036
|
except Exception as exc:
|
|
992
|
-
|
|
1037
|
+
logger.exception("ide_status failed")
|
|
1038
|
+
return {"error": "internal error", "ides": []}
|
|
993
1039
|
|
|
994
1040
|
|
|
995
1041
|
@router.post("/ide/connect")
|
|
@@ -1009,7 +1055,7 @@ async def ide_connect(request: Request):
|
|
|
1009
1055
|
results = connector.connect_all()
|
|
1010
1056
|
return {"results": results}
|
|
1011
1057
|
except Exception as e:
|
|
1012
|
-
return
|
|
1058
|
+
return _internal_error()
|
|
1013
1059
|
|
|
1014
1060
|
|
|
1015
1061
|
# ── Active Memory (V3.1) ────────────────────────────────────
|
|
@@ -1027,7 +1073,8 @@ async def learning_signals():
|
|
|
1027
1073
|
pid = config.active_profile
|
|
1028
1074
|
return {"success": True, **signals.get_signal_stats(pid)}
|
|
1029
1075
|
except Exception as exc:
|
|
1030
|
-
|
|
1076
|
+
logger.exception("learning_signals failed")
|
|
1077
|
+
return {"success": False, "error": "internal error"}
|
|
1031
1078
|
|
|
1032
1079
|
|
|
1033
1080
|
@router.post("/learning/consolidate")
|
|
@@ -1078,7 +1125,8 @@ async def run_consolidation(request: Request):
|
|
|
1078
1125
|
except HTTPException:
|
|
1079
1126
|
raise
|
|
1080
1127
|
except Exception as exc:
|
|
1081
|
-
|
|
1128
|
+
logger.exception("run_consolidation failed")
|
|
1129
|
+
return {"success": False, "error": "internal error"}
|
|
1082
1130
|
|
|
1083
1131
|
|
|
1084
1132
|
@router.get("/hooks/status")
|
|
@@ -1088,7 +1136,8 @@ async def hooks_status():
|
|
|
1088
1136
|
from superlocalmemory.hooks.claude_code_hooks import check_status
|
|
1089
1137
|
return {"success": True, **check_status()}
|
|
1090
1138
|
except Exception as exc:
|
|
1091
|
-
|
|
1139
|
+
logger.exception("hooks_status failed")
|
|
1140
|
+
return {"success": False, "error": "internal error"}
|
|
1092
1141
|
|
|
1093
1142
|
|
|
1094
1143
|
# ── Phase 6: V3.2 API Endpoints ──────────────────────────────
|
|
@@ -1153,7 +1202,7 @@ async def get_auto_invoke_config(request: Request):
|
|
|
1153
1202
|
"last_invocation": persisted.get("last_invocation", None),
|
|
1154
1203
|
}
|
|
1155
1204
|
except Exception as e:
|
|
1156
|
-
return
|
|
1205
|
+
return _internal_error()
|
|
1157
1206
|
|
|
1158
1207
|
|
|
1159
1208
|
# ── 2. PUT /api/v3/auto-invoke/config ─────────────────────────
|
|
@@ -1192,7 +1241,7 @@ async def set_auto_invoke_config(request: Request):
|
|
|
1192
1241
|
|
|
1193
1242
|
return updated
|
|
1194
1243
|
except Exception as e:
|
|
1195
|
-
return
|
|
1244
|
+
return _internal_error()
|
|
1196
1245
|
|
|
1197
1246
|
|
|
1198
1247
|
# ── 3. GET /api/v3/associations ───────────────────────────────
|
|
@@ -1264,7 +1313,7 @@ async def get_associations(
|
|
|
1264
1313
|
|
|
1265
1314
|
return {"edges": edges, "total": total}
|
|
1266
1315
|
except Exception as e:
|
|
1267
|
-
return
|
|
1316
|
+
return _internal_error()
|
|
1268
1317
|
|
|
1269
1318
|
|
|
1270
1319
|
# ── 4. GET /api/v3/associations/stats ─────────────────────────
|
|
@@ -1365,7 +1414,7 @@ async def get_association_stats(request: Request, profile: str = ""):
|
|
|
1365
1414
|
"top_connected_facts": top_facts,
|
|
1366
1415
|
}
|
|
1367
1416
|
except Exception as e:
|
|
1368
|
-
return
|
|
1417
|
+
return _internal_error()
|
|
1369
1418
|
|
|
1370
1419
|
|
|
1371
1420
|
# ── 5. GET /api/v3/consolidation/status ───────────────────────
|
|
@@ -1452,7 +1501,7 @@ async def get_consolidation_status(request: Request, profile: str = ""):
|
|
|
1452
1501
|
conn.close()
|
|
1453
1502
|
return result
|
|
1454
1503
|
except Exception as e:
|
|
1455
|
-
return
|
|
1504
|
+
return _internal_error()
|
|
1456
1505
|
|
|
1457
1506
|
|
|
1458
1507
|
# ── 6. POST /api/v3/consolidation/trigger ─────────────────────
|
|
@@ -1486,42 +1535,54 @@ async def trigger_consolidation(request: Request):
|
|
|
1486
1535
|
# path unconditionally. That dead branch is removed; consolidation
|
|
1487
1536
|
# always runs directly against a lease-protected DB connection so a
|
|
1488
1537
|
# concurrent profile switch cannot commit mid-consolidation.
|
|
1538
|
+
#
|
|
1539
|
+
# v3.4.64: ConsolidationEngine.consolidate() is CPU/IO bound (seconds
|
|
1540
|
+
# to minutes). Calling it directly in an async route blocks the ASGI
|
|
1541
|
+
# event loop. Moved into asyncio.to_thread() so the event loop stays
|
|
1542
|
+
# live. The runtime.operation() lease is acquired INSIDE the thread —
|
|
1543
|
+
# blocking a thread is fine; blocking the event loop is not.
|
|
1544
|
+
import asyncio as _asyncio
|
|
1489
1545
|
from superlocalmemory.core.config import SLMConfig
|
|
1490
1546
|
from superlocalmemory.storage.database import DatabaseManager
|
|
1491
1547
|
from superlocalmemory.storage import schema as _schema
|
|
1492
1548
|
from superlocalmemory.core.consolidation_engine import ConsolidationEngine
|
|
1493
1549
|
from superlocalmemory.server.profile_runtime import get_profile_runtime
|
|
1494
1550
|
|
|
1495
|
-
|
|
1496
|
-
with runtime.operation():
|
|
1497
|
-
config = SLMConfig.load()
|
|
1498
|
-
db = DatabaseManager(config.db_path)
|
|
1499
|
-
db.initialize(_schema)
|
|
1551
|
+
_app_state = request.app.state
|
|
1500
1552
|
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
)
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
ConsolidationWorker,
|
|
1553
|
+
def _run_consolidation() -> dict:
|
|
1554
|
+
runtime = get_profile_runtime(_app_state)
|
|
1555
|
+
with runtime.operation():
|
|
1556
|
+
config = SLMConfig.load()
|
|
1557
|
+
db = DatabaseManager(config.db_path)
|
|
1558
|
+
db.initialize(_schema)
|
|
1559
|
+
engine = ConsolidationEngine(
|
|
1560
|
+
db=db, config=config.consolidation, slm_config=config,
|
|
1510
1561
|
)
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1562
|
+
res = engine.consolidate(profile_id=pid, lightweight=lightweight)
|
|
1563
|
+
# v3.4.1: Auto-trigger behavioral pattern mining after consolidation
|
|
1564
|
+
try:
|
|
1565
|
+
from superlocalmemory.learning.consolidation_worker import (
|
|
1566
|
+
ConsolidationWorker,
|
|
1567
|
+
)
|
|
1568
|
+
learning_db = config.base_dir / "learning.db"
|
|
1569
|
+
cw = ConsolidationWorker(str(config.db_path), str(learning_db))
|
|
1570
|
+
pattern_count = cw._generate_patterns(pid, False)
|
|
1571
|
+
res["patterns_mined"] = pattern_count
|
|
1572
|
+
logger.info(
|
|
1573
|
+
"Auto-mined %d patterns after consolidation", pattern_count
|
|
1574
|
+
)
|
|
1575
|
+
except Exception as exc:
|
|
1576
|
+
logger.debug("Pattern mining after consolidation failed: %s", exc)
|
|
1577
|
+
return res
|
|
1518
1578
|
|
|
1579
|
+
result = await _asyncio.to_thread(_run_consolidation)
|
|
1519
1580
|
authorization.complete()
|
|
1520
1581
|
return {"success": True, **result}
|
|
1521
1582
|
except HTTPException:
|
|
1522
1583
|
raise
|
|
1523
1584
|
except Exception as e:
|
|
1524
|
-
return
|
|
1585
|
+
return _internal_error()
|
|
1525
1586
|
|
|
1526
1587
|
|
|
1527
1588
|
# ── 7. GET /api/v3/core-memory ────────────────────────────────
|
|
@@ -1567,7 +1628,7 @@ async def get_core_memory(request: Request, profile: str = ""):
|
|
|
1567
1628
|
|
|
1568
1629
|
return {"blocks": blocks, "total_chars": total_chars, "char_limit": 2000}
|
|
1569
1630
|
except Exception as e:
|
|
1570
|
-
return
|
|
1631
|
+
return _internal_error()
|
|
1571
1632
|
|
|
1572
1633
|
|
|
1573
1634
|
# ── 8. PUT /api/v3/core-memory/{block_id} ─────────────────────
|
|
@@ -1650,7 +1711,7 @@ async def update_core_memory_block(block_id: str, request: Request):
|
|
|
1650
1711
|
except HTTPException:
|
|
1651
1712
|
raise
|
|
1652
1713
|
except Exception as e:
|
|
1653
|
-
return
|
|
1714
|
+
return _internal_error()
|
|
1654
1715
|
|
|
1655
1716
|
|
|
1656
1717
|
# ── 9. GET /api/v3/vector-store/status ────────────────────────
|
|
@@ -1697,7 +1758,7 @@ async def get_vector_store_status(request: Request, profile: str = ""):
|
|
|
1697
1758
|
|
|
1698
1759
|
return result
|
|
1699
1760
|
except Exception as e:
|
|
1700
|
-
return
|
|
1761
|
+
return _internal_error()
|
|
1701
1762
|
|
|
1702
1763
|
|
|
1703
1764
|
# ── Phase 10: V3.3 API Endpoints ────────────────────────────
|
|
@@ -1752,7 +1813,7 @@ async def forgetting_stats(request: Request, profile: str = ""):
|
|
|
1752
1813
|
conn.close()
|
|
1753
1814
|
return {"total": total, "zones": zones}
|
|
1754
1815
|
except Exception as e:
|
|
1755
|
-
return
|
|
1816
|
+
return _internal_error()
|
|
1756
1817
|
|
|
1757
1818
|
|
|
1758
1819
|
# ── 1b. POST /api/v3/forgetting/run ─────────────────────────
|
|
@@ -1828,8 +1889,9 @@ async def run_forgetting(request: Request):
|
|
|
1828
1889
|
|
|
1829
1890
|
conn.commit()
|
|
1830
1891
|
except Exception as exc:
|
|
1892
|
+
logger.exception("run_forgetting decay failed")
|
|
1831
1893
|
conn.close()
|
|
1832
|
-
return {"success": False, "error":
|
|
1894
|
+
return {"success": False, "error": "internal error"}
|
|
1833
1895
|
|
|
1834
1896
|
conn.close()
|
|
1835
1897
|
authorization.complete()
|
|
@@ -1837,7 +1899,7 @@ async def run_forgetting(request: Request):
|
|
|
1837
1899
|
except HTTPException:
|
|
1838
1900
|
raise
|
|
1839
1901
|
except Exception as e:
|
|
1840
|
-
return
|
|
1902
|
+
return _internal_error()
|
|
1841
1903
|
|
|
1842
1904
|
|
|
1843
1905
|
# ── 1c. GET /api/v3/quantization/stats ──────────────────────
|
|
@@ -1900,7 +1962,7 @@ async def quantization_stats(request: Request, profile: str = ""):
|
|
|
1900
1962
|
conn.close()
|
|
1901
1963
|
return {"total": total, "tiers": tiers, "compression_ratio": compression_ratio}
|
|
1902
1964
|
except Exception as e:
|
|
1903
|
-
return
|
|
1965
|
+
return _internal_error()
|
|
1904
1966
|
|
|
1905
1967
|
|
|
1906
1968
|
# ── 1d. GET /api/v3/ccq/blocks ──────────────────────────────
|
|
@@ -1960,7 +2022,7 @@ async def ccq_blocks(request: Request, profile: str = "", limit: int = 50):
|
|
|
1960
2022
|
conn.close()
|
|
1961
2023
|
return {"blocks": blocks, "total": total}
|
|
1962
2024
|
except Exception as e:
|
|
1963
|
-
return
|
|
2025
|
+
return _internal_error()
|
|
1964
2026
|
|
|
1965
2027
|
|
|
1966
2028
|
# ── 1e. GET /api/v3/soft-prompts ─────────────────────────────
|
|
@@ -2013,7 +2075,7 @@ async def get_soft_prompts(request: Request, profile: str = ""):
|
|
|
2013
2075
|
conn.close()
|
|
2014
2076
|
return {"prompts": prompts, "total": len(prompts), "total_tokens": total_tokens}
|
|
2015
2077
|
except Exception as e:
|
|
2016
|
-
return
|
|
2078
|
+
return _internal_error()
|
|
2017
2079
|
|
|
2018
2080
|
|
|
2019
2081
|
# ── 1f. GET /api/v3/health/processes ─────────────────────────
|
|
@@ -2060,7 +2122,7 @@ async def process_health(request: Request):
|
|
|
2060
2122
|
"healthy": processes["parent"]["status"] != "dead",
|
|
2061
2123
|
}
|
|
2062
2124
|
except Exception as e:
|
|
2063
|
-
return
|
|
2125
|
+
return _internal_error()
|
|
2064
2126
|
|
|
2065
2127
|
|
|
2066
2128
|
# ── 1g. GET /api/v3/v33/overview ─────────────────────────────
|
|
@@ -2192,7 +2254,7 @@ async def get_graph_communities(request: Request, profile: str = ""):
|
|
|
2192
2254
|
return {"communities": communities, "total": len(communities)}
|
|
2193
2255
|
|
|
2194
2256
|
except Exception as e:
|
|
2195
|
-
return
|
|
2257
|
+
return _internal_error()
|
|
2196
2258
|
|
|
2197
2259
|
|
|
2198
2260
|
@router.post("/graph/run-communities")
|
|
@@ -2221,7 +2283,7 @@ async def run_community_detection(request: Request):
|
|
|
2221
2283
|
except HTTPException:
|
|
2222
2284
|
raise
|
|
2223
2285
|
except Exception as e:
|
|
2224
|
-
return
|
|
2286
|
+
return _internal_error()
|
|
2225
2287
|
|
|
2226
2288
|
|
|
2227
2289
|
@router.get("/v33/overview")
|
|
@@ -2354,4 +2416,56 @@ async def v33_overview(request: Request, profile: str = ""):
|
|
|
2354
2416
|
conn.close()
|
|
2355
2417
|
return overview
|
|
2356
2418
|
except Exception as e:
|
|
2357
|
-
return
|
|
2419
|
+
return _internal_error()
|
|
2420
|
+
|
|
2421
|
+
|
|
2422
|
+
# ── MCP Profiles ─────────────────────────────────────────────
|
|
2423
|
+
|
|
2424
|
+
@router.get("/mcp/profiles")
|
|
2425
|
+
async def get_mcp_profiles(request: Request):
|
|
2426
|
+
"""MCP profile summary: current profile, all profiles with tool counts and names.
|
|
2427
|
+
|
|
2428
|
+
Reads only the env var SLM_MCP_PROFILE and the pure-data profiles module.
|
|
2429
|
+
No engine required — safe to call at any point in the daemon lifecycle.
|
|
2430
|
+
|
|
2431
|
+
Note on 'current': when SLM_MCP_PROFILE is unset the MCP server falls
|
|
2432
|
+
back to the legacy _ESSENTIAL_TOOLS set. The UI reports 'core' for that
|
|
2433
|
+
state because 'core' is the recommended named-profile equivalent for new
|
|
2434
|
+
installs and is the closest documented starting point for users.
|
|
2435
|
+
"""
|
|
2436
|
+
try:
|
|
2437
|
+
from superlocalmemory.mcp.profiles import (
|
|
2438
|
+
_PROFILE_DEFINITIONS,
|
|
2439
|
+
_PROFILE_ALIASES,
|
|
2440
|
+
PROFILE_DESCRIPTIONS,
|
|
2441
|
+
)
|
|
2442
|
+
|
|
2443
|
+
raw_profile = os.environ.get("SLM_MCP_PROFILE", "").strip().lower()
|
|
2444
|
+
canonical = _PROFILE_ALIASES.get(raw_profile, raw_profile)
|
|
2445
|
+
# Blank env var, "whole", or an unknown value all resolve to "core"
|
|
2446
|
+
# for UI display purposes (safe, conservative default).
|
|
2447
|
+
if not canonical or canonical == "whole" or canonical not in _PROFILE_DEFINITIONS:
|
|
2448
|
+
current = "core"
|
|
2449
|
+
else:
|
|
2450
|
+
current = canonical
|
|
2451
|
+
|
|
2452
|
+
profiles_out: dict = {}
|
|
2453
|
+
for name, tool_set in _PROFILE_DEFINITIONS.items():
|
|
2454
|
+
profiles_out[name] = {
|
|
2455
|
+
"count": len(tool_set),
|
|
2456
|
+
"tools": sorted(tool_set),
|
|
2457
|
+
"description": PROFILE_DESCRIPTIONS.get(name, ""),
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
all_tools: set = set()
|
|
2461
|
+
for tool_set in _PROFILE_DEFINITIONS.values():
|
|
2462
|
+
all_tools.update(tool_set)
|
|
2463
|
+
|
|
2464
|
+
return {
|
|
2465
|
+
"current": current,
|
|
2466
|
+
"profiles": profiles_out,
|
|
2467
|
+
"aliases": dict(_PROFILE_ALIASES),
|
|
2468
|
+
"total_tools": len(all_tools),
|
|
2469
|
+
}
|
|
2470
|
+
except Exception:
|
|
2471
|
+
return _internal_error()
|
|
@@ -6,11 +6,13 @@
|
|
|
6
6
|
|
|
7
7
|
Routes: /ws/updates
|
|
8
8
|
"""
|
|
9
|
+
import logging
|
|
9
10
|
from typing import Set
|
|
10
11
|
from datetime import datetime, timezone
|
|
11
12
|
|
|
12
13
|
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
|
|
13
14
|
|
|
15
|
+
logger = logging.getLogger("superlocalmemory.routes.ws")
|
|
14
16
|
router = APIRouter()
|
|
15
17
|
|
|
16
18
|
|
|
@@ -71,10 +73,11 @@ async def websocket_updates(websocket: WebSocket):
|
|
|
71
73
|
|
|
72
74
|
except WebSocketDisconnect:
|
|
73
75
|
break
|
|
74
|
-
except Exception
|
|
76
|
+
except Exception:
|
|
77
|
+
logger.exception("ws route error")
|
|
75
78
|
await websocket.send_json({
|
|
76
79
|
"type": "error",
|
|
77
|
-
"message":
|
|
80
|
+
"message": "Internal server error",
|
|
78
81
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
79
82
|
})
|
|
80
83
|
|