superlocalmemory 3.7.7 → 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 +85 -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 -2
- 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 +96 -12
- 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/auth_middleware.py +28 -0
- 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 +50 -5
- 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 +21 -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 +72 -16
- 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 +221 -49
- 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 +194 -81
- package/src/superlocalmemory/server/routes/ws.py +5 -2
- package/src/superlocalmemory/server/security_middleware.py +12 -5
- package/src/superlocalmemory/server/ui.py +30 -5
- package/src/superlocalmemory/server/unified_daemon.py +431 -75
- 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_v32.py +0 -9
- 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 ─────────────────────
|
|
@@ -1462,7 +1511,8 @@ async def trigger_consolidation(request: Request):
|
|
|
1462
1511
|
"""Trigger consolidation manually.
|
|
1463
1512
|
|
|
1464
1513
|
Body: {"lightweight": false, "profile": ""}
|
|
1465
|
-
|
|
1514
|
+
Runs under the daemon's profile-runtime operation lease (Rule 18) so a
|
|
1515
|
+
concurrent profile switch cannot commit mid-consolidation.
|
|
1466
1516
|
"""
|
|
1467
1517
|
try:
|
|
1468
1518
|
body = await request.json()
|
|
@@ -1478,51 +1528,61 @@ async def trigger_consolidation(request: Request):
|
|
|
1478
1528
|
profile_id=pid,
|
|
1479
1529
|
)
|
|
1480
1530
|
|
|
1481
|
-
#
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
# Fallback: direct consolidation if WorkerPool unavailable
|
|
1531
|
+
# v3.7.8 SEC-M-01: the prior "WorkerPool" fast path called
|
|
1532
|
+
# ``pool.send_command(...)``, a method that does not exist on
|
|
1533
|
+
# WorkerPool — every call raised AttributeError, was silently
|
|
1534
|
+
# swallowed by the bare ``except``, and fell through to this direct
|
|
1535
|
+
# path unconditionally. That dead branch is removed; consolidation
|
|
1536
|
+
# always runs directly against a lease-protected DB connection so a
|
|
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
|
|
1497
1545
|
from superlocalmemory.core.config import SLMConfig
|
|
1498
1546
|
from superlocalmemory.storage.database import DatabaseManager
|
|
1499
1547
|
from superlocalmemory.storage import schema as _schema
|
|
1500
1548
|
from superlocalmemory.core.consolidation_engine import ConsolidationEngine
|
|
1549
|
+
from superlocalmemory.server.profile_runtime import get_profile_runtime
|
|
1501
1550
|
|
|
1502
|
-
|
|
1503
|
-
db = DatabaseManager(config.db_path)
|
|
1504
|
-
db.initialize(_schema)
|
|
1505
|
-
|
|
1506
|
-
engine = ConsolidationEngine(db=db, config=config.consolidation, slm_config=config)
|
|
1507
|
-
result = engine.consolidate(profile_id=pid, lightweight=lightweight)
|
|
1551
|
+
_app_state = request.app.state
|
|
1508
1552
|
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
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,
|
|
1561
|
+
)
|
|
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
|
|
1519
1578
|
|
|
1579
|
+
result = await _asyncio.to_thread(_run_consolidation)
|
|
1520
1580
|
authorization.complete()
|
|
1521
1581
|
return {"success": True, **result}
|
|
1522
1582
|
except HTTPException:
|
|
1523
1583
|
raise
|
|
1524
1584
|
except Exception as e:
|
|
1525
|
-
return
|
|
1585
|
+
return _internal_error()
|
|
1526
1586
|
|
|
1527
1587
|
|
|
1528
1588
|
# ── 7. GET /api/v3/core-memory ────────────────────────────────
|
|
@@ -1568,7 +1628,7 @@ async def get_core_memory(request: Request, profile: str = ""):
|
|
|
1568
1628
|
|
|
1569
1629
|
return {"blocks": blocks, "total_chars": total_chars, "char_limit": 2000}
|
|
1570
1630
|
except Exception as e:
|
|
1571
|
-
return
|
|
1631
|
+
return _internal_error()
|
|
1572
1632
|
|
|
1573
1633
|
|
|
1574
1634
|
# ── 8. PUT /api/v3/core-memory/{block_id} ─────────────────────
|
|
@@ -1651,7 +1711,7 @@ async def update_core_memory_block(block_id: str, request: Request):
|
|
|
1651
1711
|
except HTTPException:
|
|
1652
1712
|
raise
|
|
1653
1713
|
except Exception as e:
|
|
1654
|
-
return
|
|
1714
|
+
return _internal_error()
|
|
1655
1715
|
|
|
1656
1716
|
|
|
1657
1717
|
# ── 9. GET /api/v3/vector-store/status ────────────────────────
|
|
@@ -1698,7 +1758,7 @@ async def get_vector_store_status(request: Request, profile: str = ""):
|
|
|
1698
1758
|
|
|
1699
1759
|
return result
|
|
1700
1760
|
except Exception as e:
|
|
1701
|
-
return
|
|
1761
|
+
return _internal_error()
|
|
1702
1762
|
|
|
1703
1763
|
|
|
1704
1764
|
# ── Phase 10: V3.3 API Endpoints ────────────────────────────
|
|
@@ -1753,7 +1813,7 @@ async def forgetting_stats(request: Request, profile: str = ""):
|
|
|
1753
1813
|
conn.close()
|
|
1754
1814
|
return {"total": total, "zones": zones}
|
|
1755
1815
|
except Exception as e:
|
|
1756
|
-
return
|
|
1816
|
+
return _internal_error()
|
|
1757
1817
|
|
|
1758
1818
|
|
|
1759
1819
|
# ── 1b. POST /api/v3/forgetting/run ─────────────────────────
|
|
@@ -1829,8 +1889,9 @@ async def run_forgetting(request: Request):
|
|
|
1829
1889
|
|
|
1830
1890
|
conn.commit()
|
|
1831
1891
|
except Exception as exc:
|
|
1892
|
+
logger.exception("run_forgetting decay failed")
|
|
1832
1893
|
conn.close()
|
|
1833
|
-
return {"success": False, "error":
|
|
1894
|
+
return {"success": False, "error": "internal error"}
|
|
1834
1895
|
|
|
1835
1896
|
conn.close()
|
|
1836
1897
|
authorization.complete()
|
|
@@ -1838,7 +1899,7 @@ async def run_forgetting(request: Request):
|
|
|
1838
1899
|
except HTTPException:
|
|
1839
1900
|
raise
|
|
1840
1901
|
except Exception as e:
|
|
1841
|
-
return
|
|
1902
|
+
return _internal_error()
|
|
1842
1903
|
|
|
1843
1904
|
|
|
1844
1905
|
# ── 1c. GET /api/v3/quantization/stats ──────────────────────
|
|
@@ -1901,7 +1962,7 @@ async def quantization_stats(request: Request, profile: str = ""):
|
|
|
1901
1962
|
conn.close()
|
|
1902
1963
|
return {"total": total, "tiers": tiers, "compression_ratio": compression_ratio}
|
|
1903
1964
|
except Exception as e:
|
|
1904
|
-
return
|
|
1965
|
+
return _internal_error()
|
|
1905
1966
|
|
|
1906
1967
|
|
|
1907
1968
|
# ── 1d. GET /api/v3/ccq/blocks ──────────────────────────────
|
|
@@ -1961,7 +2022,7 @@ async def ccq_blocks(request: Request, profile: str = "", limit: int = 50):
|
|
|
1961
2022
|
conn.close()
|
|
1962
2023
|
return {"blocks": blocks, "total": total}
|
|
1963
2024
|
except Exception as e:
|
|
1964
|
-
return
|
|
2025
|
+
return _internal_error()
|
|
1965
2026
|
|
|
1966
2027
|
|
|
1967
2028
|
# ── 1e. GET /api/v3/soft-prompts ─────────────────────────────
|
|
@@ -2014,7 +2075,7 @@ async def get_soft_prompts(request: Request, profile: str = ""):
|
|
|
2014
2075
|
conn.close()
|
|
2015
2076
|
return {"prompts": prompts, "total": len(prompts), "total_tokens": total_tokens}
|
|
2016
2077
|
except Exception as e:
|
|
2017
|
-
return
|
|
2078
|
+
return _internal_error()
|
|
2018
2079
|
|
|
2019
2080
|
|
|
2020
2081
|
# ── 1f. GET /api/v3/health/processes ─────────────────────────
|
|
@@ -2061,7 +2122,7 @@ async def process_health(request: Request):
|
|
|
2061
2122
|
"healthy": processes["parent"]["status"] != "dead",
|
|
2062
2123
|
}
|
|
2063
2124
|
except Exception as e:
|
|
2064
|
-
return
|
|
2125
|
+
return _internal_error()
|
|
2065
2126
|
|
|
2066
2127
|
|
|
2067
2128
|
# ── 1g. GET /api/v3/v33/overview ─────────────────────────────
|
|
@@ -2193,7 +2254,7 @@ async def get_graph_communities(request: Request, profile: str = ""):
|
|
|
2193
2254
|
return {"communities": communities, "total": len(communities)}
|
|
2194
2255
|
|
|
2195
2256
|
except Exception as e:
|
|
2196
|
-
return
|
|
2257
|
+
return _internal_error()
|
|
2197
2258
|
|
|
2198
2259
|
|
|
2199
2260
|
@router.post("/graph/run-communities")
|
|
@@ -2222,7 +2283,7 @@ async def run_community_detection(request: Request):
|
|
|
2222
2283
|
except HTTPException:
|
|
2223
2284
|
raise
|
|
2224
2285
|
except Exception as e:
|
|
2225
|
-
return
|
|
2286
|
+
return _internal_error()
|
|
2226
2287
|
|
|
2227
2288
|
|
|
2228
2289
|
@router.get("/v33/overview")
|
|
@@ -2355,4 +2416,56 @@ async def v33_overview(request: Request, profile: str = ""):
|
|
|
2355
2416
|
conn.close()
|
|
2356
2417
|
return overview
|
|
2357
2418
|
except Exception as e:
|
|
2358
|
-
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()
|