superlocalmemory 3.6.23 → 3.7.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/CHANGELOG.md +52 -0
- package/README.md +271 -71
- package/bin/slm-npm +43 -89
- package/ide/configs/antigravity-mcp.json +2 -2
- package/ide/configs/chatgpt-desktop-mcp.json +1 -1
- package/ide/configs/claude-desktop-mcp.json +2 -2
- package/ide/configs/windsurf-mcp.json +2 -2
- package/ide/hooks/context-hook.js +6 -2
- package/ide/hooks/post-recall-hook.js +7 -3
- package/ide/hooks/tool-event-hook.sh +2 -1
- package/package.json +18 -10
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/_GENERATED.md +1 -1
- package/plugin/agents/slm-memory-advisor.md +1 -1
- package/plugin/requirements.txt +1 -1
- package/plugin/skills/slm-session/SKILL.md +1 -1
- package/plugin-src/rules/AGENTS.md +1 -1
- package/pyproject.toml +40 -8
- package/scripts/postinstall-interactive.js +17 -94
- package/scripts/postinstall.js +185 -258
- package/scripts/preuninstall.js +9 -50
- package/src/superlocalmemory/__init__.py +2 -2
- package/src/superlocalmemory/attribution/mathematical_dna.py +1 -1
- package/src/superlocalmemory/attribution/signer.py +34 -19
- package/src/superlocalmemory/attribution/watermark.py +1 -1
- package/src/superlocalmemory/cli/_lazy_init.py +3 -5
- package/src/superlocalmemory/cli/commands.py +453 -191
- package/src/superlocalmemory/cli/context_commands.py +5 -4
- package/src/superlocalmemory/cli/daemon.py +282 -187
- package/src/superlocalmemory/cli/db_migrate.py +3 -1
- package/src/superlocalmemory/cli/diagnostics_cmd.py +28 -0
- package/src/superlocalmemory/cli/evidence_cmd.py +103 -0
- package/src/superlocalmemory/cli/ingest_cmd.py +7 -3
- package/src/superlocalmemory/cli/main.py +128 -31
- package/src/superlocalmemory/cli/pending_store.py +54 -38
- package/src/superlocalmemory/cli/scale_engine_cmd.py +37 -0
- package/src/superlocalmemory/cli/service_installer.py +57 -52
- package/src/superlocalmemory/cli/setup_wizard.py +142 -88
- package/src/superlocalmemory/cli/version_banner.py +2 -1
- package/src/superlocalmemory/code_graph/config.py +3 -1
- package/src/superlocalmemory/core/backend_orchestrator.py +81 -21
- package/src/superlocalmemory/core/config.py +65 -20
- package/src/superlocalmemory/core/consolidation_engine.py +9 -7
- package/src/superlocalmemory/core/context_cache.py +56 -8
- package/src/superlocalmemory/core/derivation_lineage.py +246 -0
- package/src/superlocalmemory/core/embedding_worker.py +32 -20
- package/src/superlocalmemory/core/embeddings.py +54 -18
- package/src/superlocalmemory/core/engine.py +150 -104
- package/src/superlocalmemory/core/engine_ingestion.py +513 -0
- package/src/superlocalmemory/core/engine_wiring.py +2 -0
- package/src/superlocalmemory/core/evidence_bundle.py +526 -0
- package/src/superlocalmemory/core/fact_consolidator.py +5 -11
- package/src/superlocalmemory/core/graph_analyzer.py +2 -2
- package/src/superlocalmemory/core/health_monitor.py +4 -2
- package/src/superlocalmemory/core/ingestion_command.py +636 -0
- package/src/superlocalmemory/core/injection.py +69 -18
- package/src/superlocalmemory/core/lifecycle_state.py +153 -0
- package/src/superlocalmemory/core/maintenance.py +1 -1
- package/src/superlocalmemory/core/maintenance_scheduler.py +51 -35
- package/src/superlocalmemory/core/mutations.py +143 -0
- package/src/superlocalmemory/core/platform_utils.py +7 -4
- package/src/superlocalmemory/core/ram_lock.py +16 -5
- package/src/superlocalmemory/core/rate_limit.py +1 -1
- package/src/superlocalmemory/core/recall_pipeline.py +60 -101
- package/src/superlocalmemory/core/recall_worker.py +76 -59
- package/src/superlocalmemory/core/registry.py +1 -1
- package/src/superlocalmemory/core/scale_engine.py +293 -0
- package/src/superlocalmemory/core/score_contract.py +62 -0
- package/src/superlocalmemory/core/security_primitives.py +3 -1
- package/src/superlocalmemory/core/slm_disabled.py +3 -5
- package/src/superlocalmemory/core/store_pipeline.py +172 -40
- package/src/superlocalmemory/core/tier_manager.py +32 -20
- package/src/superlocalmemory/core/worker_pool.py +13 -4
- package/src/superlocalmemory/dynamics/activation_guided_quantization.py +1 -1
- package/src/superlocalmemory/dynamics/eap_scheduler.py +10 -3
- package/src/superlocalmemory/dynamics/ebbinghaus_langevin_coupling.py +1 -1
- package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +1 -1
- package/src/superlocalmemory/encoding/auto_linker.py +1 -1
- package/src/superlocalmemory/encoding/cognitive_consolidator.py +7 -16
- package/src/superlocalmemory/encoding/consolidator.py +22 -5
- package/src/superlocalmemory/encoding/fact_extractor.py +1 -1
- package/src/superlocalmemory/encoding/foresight.py +2 -0
- package/src/superlocalmemory/encoding/graph_builder.py +1 -1
- package/src/superlocalmemory/encoding/temporal_parser.py +2 -0
- package/src/superlocalmemory/evaluation/__init__.py +13 -0
- package/src/superlocalmemory/evaluation/calibration.py +308 -0
- package/src/superlocalmemory/evolution/skill_evolver.py +2 -1
- package/src/superlocalmemory/graph/cozo_backend.py +256 -23
- package/src/superlocalmemory/hooks/_outcome_common.py +21 -11
- package/src/superlocalmemory/hooks/antigravity_adapter.py +10 -31
- package/src/superlocalmemory/hooks/auto_invoker.py +25 -27
- package/src/superlocalmemory/hooks/auto_recall.py +31 -6
- package/src/superlocalmemory/hooks/auto_recall_hook.py +13 -33
- package/src/superlocalmemory/hooks/before_web_hook.py +9 -7
- package/src/superlocalmemory/hooks/claude_code_hooks.py +123 -35
- package/src/superlocalmemory/hooks/codex_assets.py +59 -0
- package/src/superlocalmemory/hooks/codex_hooks.py +186 -0
- package/src/superlocalmemory/hooks/context_payload.py +1 -1
- package/src/superlocalmemory/hooks/copilot_adapter.py +9 -24
- package/src/superlocalmemory/hooks/cursor_adapter.py +10 -32
- package/src/superlocalmemory/hooks/hook_daemon.py +4 -2
- package/src/superlocalmemory/hooks/hook_handlers.py +219 -32
- package/src/superlocalmemory/hooks/memory_protocol.py +5 -3
- package/src/superlocalmemory/hooks/post_tool_async_hook.py +4 -2
- package/src/superlocalmemory/hooks/session_registry.py +15 -8
- package/src/superlocalmemory/hooks/stop_outcome_hook.py +10 -6
- package/src/superlocalmemory/hooks/topic_shift_hook.py +42 -12
- package/src/superlocalmemory/hooks/user_prompt_hook.py +9 -14
- package/src/superlocalmemory/hooks/user_prompt_rehash_hook.py +19 -11
- package/src/superlocalmemory/infra/auth_middleware.py +38 -5
- package/src/superlocalmemory/infra/backup.py +7 -5
- package/src/superlocalmemory/infra/cloud_backup.py +18 -8
- package/src/superlocalmemory/infra/daemon_identity.py +248 -0
- package/src/superlocalmemory/infra/data_root.py +199 -0
- package/src/superlocalmemory/infra/event_bus.py +3 -1
- package/src/superlocalmemory/infra/local_diagnostics.py +327 -0
- package/src/superlocalmemory/infra/process_reaper.py +23 -0
- package/src/superlocalmemory/ingestion/adapter_manager.py +27 -9
- package/src/superlocalmemory/ingestion/base_adapter.py +25 -31
- package/src/superlocalmemory/ingestion/calendar_adapter.py +13 -4
- package/src/superlocalmemory/ingestion/credentials.py +14 -7
- package/src/superlocalmemory/ingestion/gmail_adapter.py +13 -4
- package/src/superlocalmemory/ingestion/transcript_adapter.py +7 -2
- package/src/superlocalmemory/learning/consolidation_quantization_worker.py +1 -1
- package/src/superlocalmemory/learning/ensemble.py +11 -0
- package/src/superlocalmemory/learning/entity_compiler.py +1 -1
- package/src/superlocalmemory/learning/feedback.py +1 -1
- package/src/superlocalmemory/learning/forgetting_scheduler.py +12 -7
- package/src/superlocalmemory/learning/quantization_scheduler.py +1 -1
- package/src/superlocalmemory/learning/ranker.py +4 -1
- package/src/superlocalmemory/learning/source_quality.py +1 -1
- package/src/superlocalmemory/learning/trigram_index.py +3 -2
- package/src/superlocalmemory/llm/backbone.py +1 -1
- package/src/superlocalmemory/math/ebbinghaus.py +1 -1
- package/src/superlocalmemory/math/fisher.py +1 -1
- package/src/superlocalmemory/math/fisher_quantized.py +1 -1
- package/src/superlocalmemory/math/hopfield.py +1 -1
- package/src/superlocalmemory/math/langevin.py +1 -1
- package/src/superlocalmemory/math/polar_quant.py +3 -4
- package/src/superlocalmemory/math/qjl.py +1 -1
- package/src/superlocalmemory/math/sheaf.py +1 -1
- package/src/superlocalmemory/math/turbo_quant.py +3 -2
- package/src/superlocalmemory/mcp/_daemon_proxy.py +12 -11
- package/src/superlocalmemory/mcp/_pool_adapter.py +27 -0
- package/src/superlocalmemory/mcp/http_transport.py +53 -0
- package/src/superlocalmemory/mcp/server.py +39 -13
- package/src/superlocalmemory/mcp/shared.py +69 -3
- package/src/superlocalmemory/mcp/tools_active.py +141 -31
- package/src/superlocalmemory/mcp/tools_core.py +128 -29
- package/src/superlocalmemory/mcp/tools_evolution.py +5 -7
- package/src/superlocalmemory/mcp/tools_learning.py +42 -2
- package/src/superlocalmemory/mcp/tools_mesh.py +7 -23
- package/src/superlocalmemory/mcp/tools_optimize.py +8 -1
- package/src/superlocalmemory/mcp/tools_v28.py +23 -2
- package/src/superlocalmemory/mcp/tools_v3.py +26 -1
- package/src/superlocalmemory/mcp/tools_v33.py +56 -17
- package/src/superlocalmemory/mesh/broker.py +2 -0
- package/src/superlocalmemory/mesh/remote_sync.py +50 -12
- package/src/superlocalmemory/optimize/cache/manager.py +77 -1
- package/src/superlocalmemory/optimize/cache/semantic.py +23 -3
- package/src/superlocalmemory/optimize/compress/ccr.py +4 -0
- package/src/superlocalmemory/optimize/compress/router.py +6 -1
- package/src/superlocalmemory/optimize/config/__init__.py +5 -0
- package/src/superlocalmemory/optimize/config/store.py +6 -4
- package/src/superlocalmemory/optimize/proxy/_helpers.py +15 -5
- package/src/superlocalmemory/optimize/proxy/capture.py +3 -2
- package/src/superlocalmemory/optimize/proxy/server.py +2 -2
- package/src/superlocalmemory/optimize/storage/db.py +12 -12
- package/src/superlocalmemory/retrieval/agentic.py +1 -1
- package/src/superlocalmemory/retrieval/ann_index.py +1 -1
- package/src/superlocalmemory/retrieval/bm25_channel.py +35 -11
- package/src/superlocalmemory/retrieval/bridge_discovery.py +73 -8
- package/src/superlocalmemory/retrieval/engine.py +169 -79
- package/src/superlocalmemory/retrieval/entity_channel.py +289 -67
- package/src/superlocalmemory/retrieval/forgetting_filter.py +1 -1
- package/src/superlocalmemory/retrieval/fusion.py +1 -1
- package/src/superlocalmemory/retrieval/hopfield_channel.py +118 -30
- package/src/superlocalmemory/retrieval/profile_channel.py +1 -1
- package/src/superlocalmemory/retrieval/quantization_aware_search.py +16 -10
- package/src/superlocalmemory/retrieval/reranker.py +56 -20
- package/src/superlocalmemory/retrieval/scope_policy.py +85 -0
- package/src/superlocalmemory/retrieval/semantic_channel.py +122 -14
- package/src/superlocalmemory/retrieval/spreading_activation.py +141 -25
- package/src/superlocalmemory/retrieval/strategy.py +1 -1
- package/src/superlocalmemory/retrieval/temporal_channel.py +30 -15
- package/src/superlocalmemory/retrieval/vector_store.py +1 -1
- package/src/superlocalmemory/server/api.py +10 -7
- package/src/superlocalmemory/server/bandit_loops.py +4 -2
- package/src/superlocalmemory/server/recall_serializer.py +24 -0
- package/src/superlocalmemory/server/route_mutations.py +84 -0
- package/src/superlocalmemory/server/routes/agents.py +8 -6
- package/src/superlocalmemory/server/routes/brain.py +14 -12
- package/src/superlocalmemory/server/routes/chat.py +29 -12
- package/src/superlocalmemory/server/routes/data_io.py +55 -24
- package/src/superlocalmemory/server/routes/helpers.py +8 -63
- package/src/superlocalmemory/server/routes/ingest.py +53 -36
- package/src/superlocalmemory/server/routes/memories.py +104 -43
- package/src/superlocalmemory/server/routes/mesh.py +31 -0
- package/src/superlocalmemory/server/routes/profiles.py +26 -4
- package/src/superlocalmemory/server/routes/tiers.py +43 -11
- package/src/superlocalmemory/server/routes/timeline.py +5 -1
- package/src/superlocalmemory/server/routes/v3_api.py +76 -21
- package/src/superlocalmemory/server/security_middleware.py +1 -1
- package/src/superlocalmemory/server/unified_daemon.py +680 -293
- package/src/superlocalmemory/server/write_identity.py +147 -0
- package/src/superlocalmemory/storage/access_log.py +4 -3
- package/src/superlocalmemory/storage/database.py +118 -25
- package/src/superlocalmemory/storage/migration_runner.py +84 -1
- package/src/superlocalmemory/storage/migration_v33.py +1 -1
- package/src/superlocalmemory/storage/migrations/M002_model_state_history.py +6 -60
- package/src/superlocalmemory/storage/migrations/M018_ingestion_operations.py +120 -0
- package/src/superlocalmemory/storage/migrations/M019_derivation_lineage.py +54 -0
- package/src/superlocalmemory/storage/migrations/M020_model_state_integrity.py +52 -0
- package/src/superlocalmemory/storage/migrations/__init__.py +5 -0
- package/src/superlocalmemory/storage/models.py +16 -0
- package/src/superlocalmemory/storage/quantized_store.py +20 -3
- package/src/superlocalmemory/storage/v2_migrator.py +5 -3
- package/src/superlocalmemory/ui/favicon.svg +5 -0
- package/src/superlocalmemory/ui/index.html +1 -0
- package/src/superlocalmemory/ui/js/compliance.js +1 -1
- package/src/superlocalmemory/ui/js/core.js +49 -8
- package/src/superlocalmemory/ui/js/dashboard.js +23 -2
- package/src/superlocalmemory/ui/js/feedback.js +1 -1
- package/src/superlocalmemory/ui/js/graph-filters.js +1 -1
- package/src/superlocalmemory/ui/js/graph-ui.js +1 -1
- package/src/superlocalmemory/ui/js/lifecycle.js +1 -1
- package/src/superlocalmemory/ui/js/ng-mesh.js +15 -49
- package/src/superlocalmemory/ui/js/settings.js +4 -2
- package/src/superlocalmemory/vector/lancedb_backend.py +57 -9
- package/bin/slm +0 -59
- package/bin/slm.bat +0 -77
- package/bin/slm.cmd +0 -5
- package/ide/integrations/langchain/README.md +0 -106
- package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +0 -9
- package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +0 -201
- package/ide/integrations/langchain/pyproject.toml +0 -38
- package/ide/integrations/langchain/tests/__init__.py +0 -3
- package/ide/integrations/langchain/tests/test_chat_message_history.py +0 -215
- package/ide/integrations/langchain/tests/test_security.py +0 -117
- package/ide/integrations/llamaindex/README.md +0 -81
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +0 -9
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +0 -316
- package/ide/integrations/llamaindex/pyproject.toml +0 -43
- package/ide/integrations/llamaindex/tests/__init__.py +0 -3
- package/ide/integrations/llamaindex/tests/test_chat_store.py +0 -294
- package/ide/integrations/llamaindex/tests/test_security.py +0 -241
- package/plugin-src/.mcp.json +0 -12
- package/plugin-src/agents/slm-memory-advisor.md +0 -44
- package/plugin-src/agents/slm-optimize-advisor.md +0 -38
- package/plugin-src/hooks/.gitkeep +0 -0
- package/plugin-src/hooks/hooks.json +0 -23
- package/plugin-src/manifest.json +0 -25
- 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 -15
- package/plugin-src/scripts/slm-launch.bat +0 -17
- package/plugin-src/settings.json +0 -16
- package/plugin-src/skills/slm-cache/SKILL.md +0 -140
- package/plugin-src/skills/slm-compress/SKILL.md +0 -143
- package/plugin-src/skills/slm-graph/SKILL.md +0 -300
- package/plugin-src/skills/slm-recall/SKILL.md +0 -204
- package/plugin-src/skills/slm-remember/SKILL.md +0 -194
- package/plugin-src/skills/slm-session/SKILL.md +0 -207
- package/plugin-src/skills/slm-status/SKILL.md +0 -149
- package/scripts/__tests__/build-plugin.test.mjs +0 -613
- package/scripts/_savings_math.py +0 -270
- package/scripts/build-dmg.sh +0 -417
- package/scripts/build-plugin.js +0 -742
- package/scripts/build-slm-hook.ps1 +0 -40
- package/scripts/build-slm-hook.sh +0 -45
- package/scripts/build_entry.py +0 -452
- package/scripts/ci/stage5b_gate.sh +0 -50
- package/scripts/dogfood_savings.py +0 -490
- package/scripts/generate-thumbnails.py +0 -218
- package/scripts/install-skills.ps1 +0 -4
- package/scripts/install-skills.sh +0 -5
- package/scripts/install.ps1 +0 -701
- package/scripts/install.sh +0 -1015
- package/scripts/postinstall_binary.js +0 -287
- package/scripts/prepack.js +0 -33
- package/scripts/release_manifest.py +0 -273
- package/scripts/slm-hook.spec +0 -56
- package/scripts/start-dashboard.ps1 +0 -52
- package/scripts/start-dashboard.sh +0 -41
- package/scripts/sync-wiki.ps1 +0 -127
- package/scripts/sync-wiki.sh +0 -82
- package/scripts/test-dmg.sh +0 -161
- package/scripts/test-npm-package.ps1 +0 -252
- package/scripts/test-npm-package.sh +0 -207
- package/scripts/verify-install.ps1 +0 -294
- package/scripts/verify-install.sh +0 -266
- package/scripts/verify-v27.ps1 +0 -301
- package/scripts/verify-v27.sh +0 -233
- package/src/superlocalmemory.egg-info/PKG-INFO +0 -516
- package/src/superlocalmemory.egg-info/SOURCES.txt +0 -529
- package/src/superlocalmemory.egg-info/dependency_links.txt +0 -1
- package/src/superlocalmemory.egg-info/entry_points.txt +0 -2
- package/src/superlocalmemory.egg-info/requires.txt +0 -71
- package/src/superlocalmemory.egg-info/top_level.txt +0 -1
|
@@ -25,6 +25,44 @@ def _get_engine(request: Request):
|
|
|
25
25
|
return get_engine_lazy(request.app.state)
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
def _authorize_memory_mutation(
|
|
29
|
+
request: Request,
|
|
30
|
+
operation: str,
|
|
31
|
+
fact_id: str,
|
|
32
|
+
*,
|
|
33
|
+
content_preview: str = "",
|
|
34
|
+
run_pre_hook: bool = True,
|
|
35
|
+
):
|
|
36
|
+
"""Authenticate a mutation, optionally gating route-owned direct SQL."""
|
|
37
|
+
from superlocalmemory.server.write_identity import require_write_actor
|
|
38
|
+
|
|
39
|
+
actor_id = require_write_actor(
|
|
40
|
+
request,
|
|
41
|
+
getattr(request.app.state, "daemon_descriptor", None),
|
|
42
|
+
actor_kind="dashboard",
|
|
43
|
+
)
|
|
44
|
+
engine = _get_engine(request)
|
|
45
|
+
if engine is None:
|
|
46
|
+
raise HTTPException(503, detail="Engine not initialized")
|
|
47
|
+
profile_id = engine.profile_id
|
|
48
|
+
context = {
|
|
49
|
+
"operation": operation,
|
|
50
|
+
"agent_id": actor_id,
|
|
51
|
+
"source_agent_id": "dashboard",
|
|
52
|
+
"profile_id": profile_id,
|
|
53
|
+
"fact_id": fact_id,
|
|
54
|
+
}
|
|
55
|
+
if content_preview:
|
|
56
|
+
context["content_preview"] = content_preview[:100]
|
|
57
|
+
if run_pre_hook:
|
|
58
|
+
try:
|
|
59
|
+
engine._hooks.run_pre(operation, context)
|
|
60
|
+
except Exception as exc:
|
|
61
|
+
logger.warning("Dashboard %s authorization rejected: %s", operation, exc)
|
|
62
|
+
raise HTTPException(403, detail="Write authorization rejected") from exc
|
|
63
|
+
return engine, profile_id, context
|
|
64
|
+
|
|
65
|
+
|
|
28
66
|
def _preview(content: str | None) -> str:
|
|
29
67
|
"""Truncate content for preview display."""
|
|
30
68
|
if not content:
|
|
@@ -430,23 +468,24 @@ async def search_memories(request: Request, body: SearchRequest):
|
|
|
430
468
|
lambda: engine.recall(body.query, limit=body.limit, fast=False),
|
|
431
469
|
)
|
|
432
470
|
elapsed_ms = round((_time.monotonic() - t0) * 1000, 1)
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
})
|
|
471
|
+
from superlocalmemory.server.recall_serializer import (
|
|
472
|
+
recall_response_metadata,
|
|
473
|
+
serialize_recall_response,
|
|
474
|
+
)
|
|
475
|
+
results, no_confident_match = serialize_recall_response(
|
|
476
|
+
response,
|
|
477
|
+
limit=body.limit,
|
|
478
|
+
per_fact_max=300,
|
|
479
|
+
total_max=max(300, body.limit * 300),
|
|
480
|
+
)
|
|
444
481
|
return {
|
|
445
482
|
"query": body.query,
|
|
446
483
|
"results": results,
|
|
447
484
|
"total": len(results),
|
|
448
485
|
"query_type": getattr(response, "query_type", "semantic"),
|
|
449
486
|
"retrieval_time_ms": elapsed_ms,
|
|
487
|
+
"no_confident_match": no_confident_match,
|
|
488
|
+
**recall_response_metadata(response),
|
|
450
489
|
}
|
|
451
490
|
|
|
452
491
|
# Fallback: direct DB text search (engine not yet initialised)
|
|
@@ -455,18 +494,35 @@ async def search_memories(request: Request, body: SearchRequest):
|
|
|
455
494
|
cursor = conn.cursor()
|
|
456
495
|
active_profile = get_active_profile()
|
|
457
496
|
cursor.execute("""
|
|
458
|
-
SELECT fact_id
|
|
497
|
+
SELECT fact_id, content, confidence as memory_confidence,
|
|
459
498
|
fact_type as category, created_at
|
|
460
499
|
FROM atomic_facts
|
|
461
500
|
WHERE profile_id = ? AND content LIKE ?
|
|
462
501
|
ORDER BY confidence DESC LIMIT ?
|
|
463
502
|
""", (active_profile, f'%{body.query}%', body.limit))
|
|
464
|
-
|
|
503
|
+
rows = cursor.fetchall()
|
|
465
504
|
conn.close()
|
|
466
505
|
|
|
506
|
+
results = [{
|
|
507
|
+
**row,
|
|
508
|
+
"score": None,
|
|
509
|
+
"relevance_score": None,
|
|
510
|
+
"ranking_score": None,
|
|
511
|
+
"confidence": row.get("memory_confidence"),
|
|
512
|
+
"rank_position": position,
|
|
513
|
+
} for position, row in enumerate(rows, start=1)]
|
|
514
|
+
|
|
467
515
|
return {
|
|
468
516
|
"query": body.query, "results": results, "total": len(results),
|
|
469
517
|
"query_type": "text_search", "retrieval_time_ms": 0,
|
|
518
|
+
"retrieval_mode": "degraded_lexical",
|
|
519
|
+
"score_contract_version": "2",
|
|
520
|
+
"calibration_status": "uncalibrated",
|
|
521
|
+
"calibration_id": None,
|
|
522
|
+
"answer_confidence": None,
|
|
523
|
+
"abstained": not bool(results),
|
|
524
|
+
"abstention_reason": None if results else "no_candidates",
|
|
525
|
+
"no_confident_match": False,
|
|
470
526
|
}
|
|
471
527
|
|
|
472
528
|
except Exception as e:
|
|
@@ -739,22 +795,20 @@ async def get_fact_detail(request: Request, fact_id: str):
|
|
|
739
795
|
@router.delete("/api/memories/{fact_id}")
|
|
740
796
|
async def delete_memory(request: Request, fact_id: str):
|
|
741
797
|
"""Delete a specific memory (atomic fact) by ID."""
|
|
798
|
+
engine, _active_profile, hook_context = _authorize_memory_mutation(
|
|
799
|
+
request, "delete", fact_id, run_pre_hook=False,
|
|
800
|
+
)
|
|
742
801
|
try:
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
(fact_id, active_profile),
|
|
802
|
+
from superlocalmemory.core.mutations import delete_fact_authorized
|
|
803
|
+
|
|
804
|
+
result = delete_fact_authorized(
|
|
805
|
+
engine,
|
|
806
|
+
fact_id,
|
|
807
|
+
trusted_actor_id=hook_context["agent_id"],
|
|
808
|
+
source_agent_id="dashboard",
|
|
751
809
|
)
|
|
752
|
-
if not
|
|
753
|
-
conn.close()
|
|
810
|
+
if not result.get("ok"):
|
|
754
811
|
raise HTTPException(status_code=404, detail="Memory not found")
|
|
755
|
-
cursor.execute("DELETE FROM atomic_facts WHERE fact_id = ?", (fact_id,))
|
|
756
|
-
conn.commit()
|
|
757
|
-
conn.close()
|
|
758
812
|
return {"success": True, "deleted": fact_id}
|
|
759
813
|
except HTTPException:
|
|
760
814
|
raise
|
|
@@ -772,11 +826,13 @@ async def forget_memory(request: Request, fact_id: str):
|
|
|
772
826
|
future ``slm restore`` can bring it back.
|
|
773
827
|
"""
|
|
774
828
|
import json as _json
|
|
829
|
+
engine, active_profile, hook_context = _authorize_memory_mutation(
|
|
830
|
+
request, "delete", fact_id,
|
|
831
|
+
)
|
|
775
832
|
try:
|
|
776
833
|
conn = get_db_connection()
|
|
777
834
|
conn.row_factory = dict_factory
|
|
778
835
|
cursor = conn.cursor()
|
|
779
|
-
active_profile = get_active_profile()
|
|
780
836
|
cursor.execute(
|
|
781
837
|
"SELECT fact_id, content, importance, confidence, "
|
|
782
838
|
" canonical_entities_json, embedding, created_at "
|
|
@@ -813,6 +869,7 @@ async def forget_memory(request: Request, fact_id: str):
|
|
|
813
869
|
)
|
|
814
870
|
conn.commit()
|
|
815
871
|
conn.close()
|
|
872
|
+
engine._hooks.run_post("delete", hook_context)
|
|
816
873
|
return {"success": True, "fact_id": fact_id, "archived_at": archived_at}
|
|
817
874
|
except HTTPException:
|
|
818
875
|
raise
|
|
@@ -830,6 +887,9 @@ async def merge_memory(request: Request, fact_id: str):
|
|
|
830
887
|
the loser's ``merged_into`` column. The loser is archived so it
|
|
831
888
|
no longer appears in default recall. The winner is untouched.
|
|
832
889
|
"""
|
|
890
|
+
engine, active_profile, hook_context = _authorize_memory_mutation(
|
|
891
|
+
request, "delete", fact_id,
|
|
892
|
+
)
|
|
833
893
|
try:
|
|
834
894
|
body = await request.json()
|
|
835
895
|
kept = str((body or {}).get("into", "")).strip()
|
|
@@ -843,7 +903,6 @@ async def merge_memory(request: Request, fact_id: str):
|
|
|
843
903
|
conn = get_db_connection()
|
|
844
904
|
conn.row_factory = dict_factory
|
|
845
905
|
cursor = conn.cursor()
|
|
846
|
-
active_profile = get_active_profile()
|
|
847
906
|
# Both must belong to the active profile.
|
|
848
907
|
cursor.execute(
|
|
849
908
|
"SELECT fact_id FROM atomic_facts "
|
|
@@ -874,6 +933,7 @@ async def merge_memory(request: Request, fact_id: str):
|
|
|
874
933
|
)
|
|
875
934
|
conn.commit()
|
|
876
935
|
conn.close()
|
|
936
|
+
engine._hooks.run_post("delete", hook_context)
|
|
877
937
|
return {
|
|
878
938
|
"success": True,
|
|
879
939
|
"merged": fact_id,
|
|
@@ -894,23 +954,24 @@ async def edit_memory(request: Request, fact_id: str):
|
|
|
894
954
|
new_content = (body.get("content") or "").strip()
|
|
895
955
|
if not new_content:
|
|
896
956
|
raise HTTPException(status_code=400, detail="content is required")
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
(fact_id, active_profile),
|
|
957
|
+
engine, _active_profile, hook_context = _authorize_memory_mutation(
|
|
958
|
+
request,
|
|
959
|
+
"update",
|
|
960
|
+
fact_id,
|
|
961
|
+
content_preview=new_content,
|
|
962
|
+
run_pre_hook=False,
|
|
904
963
|
)
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
964
|
+
from superlocalmemory.core.mutations import update_fact_authorized
|
|
965
|
+
|
|
966
|
+
result = update_fact_authorized(
|
|
967
|
+
engine,
|
|
968
|
+
fact_id,
|
|
969
|
+
new_content,
|
|
970
|
+
trusted_actor_id=hook_context["agent_id"],
|
|
971
|
+
source_agent_id="dashboard",
|
|
911
972
|
)
|
|
912
|
-
|
|
913
|
-
|
|
973
|
+
if not result.get("ok"):
|
|
974
|
+
raise HTTPException(status_code=404, detail="Memory not found")
|
|
914
975
|
return {"success": True, "fact_id": fact_id, "content": new_content}
|
|
915
976
|
except HTTPException:
|
|
916
977
|
raise
|
|
@@ -11,6 +11,7 @@ Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
|
11
11
|
|
|
12
12
|
from __future__ import annotations
|
|
13
13
|
|
|
14
|
+
import re
|
|
14
15
|
from typing import Optional
|
|
15
16
|
|
|
16
17
|
from fastapi import APIRouter, HTTPException, Request
|
|
@@ -104,9 +105,38 @@ def _get_broker(request: Request):
|
|
|
104
105
|
)
|
|
105
106
|
if not presented or not hmac.compare_digest(presented, secret):
|
|
106
107
|
raise HTTPException(401, detail="invalid or missing credential")
|
|
108
|
+
return broker
|
|
109
|
+
|
|
110
|
+
# Loopback is a transport property, not an identity. Every mesh read and
|
|
111
|
+
# write must prove the install/API/process capability because peer inboxes,
|
|
112
|
+
# coordination state, and project paths can be sensitive.
|
|
113
|
+
from superlocalmemory.server.write_identity import require_write_actor
|
|
114
|
+
|
|
115
|
+
require_write_actor(
|
|
116
|
+
request,
|
|
117
|
+
getattr(request.app.state, "daemon_descriptor", None),
|
|
118
|
+
actor_kind="mesh-route",
|
|
119
|
+
)
|
|
107
120
|
return broker
|
|
108
121
|
|
|
109
122
|
|
|
123
|
+
_SECRET_KEY = re.compile(
|
|
124
|
+
r"(?:^|[_\-.])(api[_\-.]?key|secret|token|password|credential)(?:$|[_\-.])",
|
|
125
|
+
re.IGNORECASE,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _reject_secret_state(key: str, value: str) -> None:
|
|
130
|
+
"""Refuse secret material in the plaintext coordination store."""
|
|
131
|
+
from superlocalmemory.core.security_primitives import redact_secrets
|
|
132
|
+
|
|
133
|
+
if _SECRET_KEY.search(key) or redact_secrets(value) != value:
|
|
134
|
+
raise HTTPException(
|
|
135
|
+
422,
|
|
136
|
+
detail="Mesh state is coordination metadata; secret values are prohibited",
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
110
140
|
# -- Routes --
|
|
111
141
|
|
|
112
142
|
@router.post("/register")
|
|
@@ -197,6 +227,7 @@ async def state_set(req: StateSetRequest, request: Request):
|
|
|
197
227
|
broker = _get_broker(request)
|
|
198
228
|
if not req.key:
|
|
199
229
|
raise HTTPException(400, detail="key required")
|
|
230
|
+
_reject_secret_state(req.key, req.value)
|
|
200
231
|
return broker.set_state(req.key, req.value, req.set_by)
|
|
201
232
|
|
|
202
233
|
|
|
@@ -13,7 +13,9 @@ is kept in sync as a cache for backward compatibility.
|
|
|
13
13
|
import logging
|
|
14
14
|
from datetime import datetime, timezone
|
|
15
15
|
|
|
16
|
-
from fastapi import APIRouter, HTTPException
|
|
16
|
+
from fastapi import APIRouter, HTTPException, Request
|
|
17
|
+
|
|
18
|
+
from superlocalmemory.server.route_mutations import authorize_route_mutation
|
|
17
19
|
|
|
18
20
|
from .helpers import (
|
|
19
21
|
get_db_connection, validate_profile_name,
|
|
@@ -84,7 +86,7 @@ async def list_profiles():
|
|
|
84
86
|
|
|
85
87
|
|
|
86
88
|
@router.post("/api/profiles/{name}/switch")
|
|
87
|
-
async def switch_profile(name: str):
|
|
89
|
+
async def switch_profile(name: str, request: Request):
|
|
88
90
|
"""Switch active memory profile (persists to both config stores)."""
|
|
89
91
|
try:
|
|
90
92
|
if not validate_profile_name(name):
|
|
@@ -100,6 +102,12 @@ async def switch_profile(name: str):
|
|
|
100
102
|
detail=f"Profile '{name}' not found. Available: {available}",
|
|
101
103
|
)
|
|
102
104
|
|
|
105
|
+
authorization = authorize_route_mutation(
|
|
106
|
+
request,
|
|
107
|
+
operation="update",
|
|
108
|
+
source_agent_id="http-profile-switch",
|
|
109
|
+
profile_id=name,
|
|
110
|
+
)
|
|
103
111
|
previous = _load_profiles_json().get('active_profile', 'default')
|
|
104
112
|
set_active_profile_everywhere(name)
|
|
105
113
|
|
|
@@ -118,6 +126,7 @@ async def switch_profile(name: str):
|
|
|
118
126
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
119
127
|
})
|
|
120
128
|
|
|
129
|
+
authorization.complete()
|
|
121
130
|
return {
|
|
122
131
|
"success": True, "active_profile": name,
|
|
123
132
|
"previous_profile": previous, "memory_count": count,
|
|
@@ -131,7 +140,7 @@ async def switch_profile(name: str):
|
|
|
131
140
|
|
|
132
141
|
|
|
133
142
|
@router.post("/api/profiles/create")
|
|
134
|
-
async def create_profile(body: ProfileSwitch):
|
|
143
|
+
async def create_profile(body: ProfileSwitch, request: Request):
|
|
135
144
|
"""Create a new memory profile (writes to BOTH SQLite and profiles.json)."""
|
|
136
145
|
try:
|
|
137
146
|
name = body.profile_name
|
|
@@ -144,11 +153,17 @@ async def create_profile(body: ProfileSwitch):
|
|
|
144
153
|
if name in merged_ids:
|
|
145
154
|
raise HTTPException(status_code=409, detail=f"Profile '{name}' already exists")
|
|
146
155
|
|
|
156
|
+
authorization = authorize_route_mutation(
|
|
157
|
+
request,
|
|
158
|
+
operation="update",
|
|
159
|
+
source_agent_id="http-profile-create",
|
|
160
|
+
)
|
|
147
161
|
# Write to BOTH stores atomically
|
|
148
162
|
desc = f'Memory profile: {name}'
|
|
149
163
|
ensure_profile_in_db(name, desc)
|
|
150
164
|
ensure_profile_in_json(name, desc)
|
|
151
165
|
|
|
166
|
+
authorization.complete()
|
|
152
167
|
return {"success": True, "profile": name, "message": f"Profile '{name}' created"}
|
|
153
168
|
|
|
154
169
|
except HTTPException:
|
|
@@ -158,7 +173,7 @@ async def create_profile(body: ProfileSwitch):
|
|
|
158
173
|
|
|
159
174
|
|
|
160
175
|
@router.delete("/api/profiles/{name}")
|
|
161
|
-
async def delete_profile(name: str):
|
|
176
|
+
async def delete_profile(name: str, request: Request):
|
|
162
177
|
"""Delete a profile. Moves its memories to 'default'."""
|
|
163
178
|
try:
|
|
164
179
|
if name == 'default':
|
|
@@ -173,6 +188,12 @@ async def delete_profile(name: str):
|
|
|
173
188
|
if json_config.get('active_profile') == name:
|
|
174
189
|
raise HTTPException(status_code=400, detail="Cannot delete active profile.")
|
|
175
190
|
|
|
191
|
+
authorization = authorize_route_mutation(
|
|
192
|
+
request,
|
|
193
|
+
operation="delete",
|
|
194
|
+
source_agent_id="http-profile-delete",
|
|
195
|
+
profile_id=name,
|
|
196
|
+
)
|
|
176
197
|
# Move data to default before deleting (bypasses CASCADE)
|
|
177
198
|
conn = get_db_connection()
|
|
178
199
|
cursor = conn.cursor()
|
|
@@ -204,6 +225,7 @@ async def delete_profile(name: str):
|
|
|
204
225
|
json_config['profiles'] = profiles
|
|
205
226
|
_save_profiles_json(json_config)
|
|
206
227
|
|
|
228
|
+
authorization.complete()
|
|
207
229
|
return {
|
|
208
230
|
"success": True,
|
|
209
231
|
"message": f"Profile '{name}' deleted. {moved} memories moved to 'default'.",
|
|
@@ -18,6 +18,8 @@ from datetime import datetime, UTC
|
|
|
18
18
|
from fastapi import APIRouter, HTTPException, Request
|
|
19
19
|
from pydantic import BaseModel, Field
|
|
20
20
|
|
|
21
|
+
from superlocalmemory.server.route_mutations import authorize_route_mutation
|
|
22
|
+
|
|
21
23
|
from .helpers import DB_PATH
|
|
22
24
|
|
|
23
25
|
logger = logging.getLogger("superlocalmemory.routes.tiers")
|
|
@@ -105,6 +107,12 @@ async def evaluate_tiers_route(request: Request, profile_id: str = "default"):
|
|
|
105
107
|
profile_id = _validate_profile(profile_id)
|
|
106
108
|
|
|
107
109
|
try:
|
|
110
|
+
authorization = authorize_route_mutation(
|
|
111
|
+
request,
|
|
112
|
+
operation="update",
|
|
113
|
+
source_agent_id="http-tier-evaluate",
|
|
114
|
+
profile_id=profile_id,
|
|
115
|
+
)
|
|
108
116
|
from superlocalmemory.core.tier_manager import evaluate_tiers
|
|
109
117
|
from .helpers import get_engine_lazy
|
|
110
118
|
|
|
@@ -114,6 +122,7 @@ async def evaluate_tiers_route(request: Request, profile_id: str = "default"):
|
|
|
114
122
|
status_code=503, detail="Engine not initialized",
|
|
115
123
|
)
|
|
116
124
|
stats = evaluate_tiers(engine._db, profile_id)
|
|
125
|
+
authorization.complete()
|
|
117
126
|
return {"success": True, "stats": stats}
|
|
118
127
|
except HTTPException:
|
|
119
128
|
raise
|
|
@@ -125,12 +134,23 @@ async def evaluate_tiers_route(request: Request, profile_id: str = "default"):
|
|
|
125
134
|
|
|
126
135
|
|
|
127
136
|
@router.post("/api/tiers/pin")
|
|
128
|
-
async def pin_fact_route(
|
|
137
|
+
async def pin_fact_route(
|
|
138
|
+
body: PinRequest,
|
|
139
|
+
request: Request,
|
|
140
|
+
profile_id: str = "default",
|
|
141
|
+
):
|
|
129
142
|
"""Pin a fact to stay in active tier forever.
|
|
130
143
|
|
|
131
144
|
Validates fact exists in the specified profile before pinning.
|
|
132
145
|
"""
|
|
133
146
|
profile_id = _validate_profile(profile_id)
|
|
147
|
+
authorization = authorize_route_mutation(
|
|
148
|
+
request,
|
|
149
|
+
operation="update",
|
|
150
|
+
source_agent_id="http-tier-pin",
|
|
151
|
+
profile_id=profile_id,
|
|
152
|
+
fact_id=body.fact_id,
|
|
153
|
+
)
|
|
134
154
|
|
|
135
155
|
with _db() as conn:
|
|
136
156
|
try:
|
|
@@ -139,12 +159,12 @@ async def pin_fact_route(request: PinRequest, profile_id: str = "default"):
|
|
|
139
159
|
c.execute(
|
|
140
160
|
"SELECT fact_id FROM atomic_facts "
|
|
141
161
|
"WHERE fact_id = ? AND profile_id = ?",
|
|
142
|
-
(
|
|
162
|
+
(body.fact_id, profile_id),
|
|
143
163
|
)
|
|
144
164
|
if c.fetchone() is None:
|
|
145
165
|
raise HTTPException(
|
|
146
166
|
status_code=404,
|
|
147
|
-
detail=f"Fact {
|
|
167
|
+
detail=f"Fact {body.fact_id[:8]}... not found",
|
|
148
168
|
)
|
|
149
169
|
|
|
150
170
|
now = datetime.now(UTC).isoformat()
|
|
@@ -152,15 +172,15 @@ async def pin_fact_route(request: PinRequest, profile_id: str = "default"):
|
|
|
152
172
|
"INSERT OR REPLACE INTO pinned_facts "
|
|
153
173
|
"(fact_id, profile_id, pinned_at, reason) "
|
|
154
174
|
"VALUES (?, ?, ?, ?)",
|
|
155
|
-
(
|
|
175
|
+
(body.fact_id, profile_id, now, body.reason),
|
|
156
176
|
)
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
(request.fact_id, profile_id),
|
|
177
|
+
from superlocalmemory.core.lifecycle_state import set_fact_lifecycle_zone
|
|
178
|
+
set_fact_lifecycle_zone(
|
|
179
|
+
conn, [body.fact_id], "active", profile_id=profile_id,
|
|
161
180
|
)
|
|
162
181
|
conn.commit()
|
|
163
|
-
|
|
182
|
+
authorization.complete()
|
|
183
|
+
return {"success": True, "message": f"Fact {body.fact_id[:8]}... pinned"}
|
|
164
184
|
except HTTPException:
|
|
165
185
|
raise
|
|
166
186
|
except Exception as exc:
|
|
@@ -171,7 +191,11 @@ async def pin_fact_route(request: PinRequest, profile_id: str = "default"):
|
|
|
171
191
|
|
|
172
192
|
|
|
173
193
|
@router.post("/api/tiers/unpin")
|
|
174
|
-
async def unpin_fact_route(
|
|
194
|
+
async def unpin_fact_route(
|
|
195
|
+
body: PinRequest,
|
|
196
|
+
request: Request,
|
|
197
|
+
profile_id: str = "default",
|
|
198
|
+
):
|
|
175
199
|
"""Unpin a fact, allowing normal tier demotion.
|
|
176
200
|
|
|
177
201
|
Lifecycle stays 'active' until the next tier evaluation cycle demotes it
|
|
@@ -179,14 +203,22 @@ async def unpin_fact_route(request: PinRequest, profile_id: str = "default"):
|
|
|
179
203
|
surprise the user.
|
|
180
204
|
"""
|
|
181
205
|
profile_id = _validate_profile(profile_id)
|
|
206
|
+
authorization = authorize_route_mutation(
|
|
207
|
+
request,
|
|
208
|
+
operation="update",
|
|
209
|
+
source_agent_id="http-tier-unpin",
|
|
210
|
+
profile_id=profile_id,
|
|
211
|
+
fact_id=body.fact_id,
|
|
212
|
+
)
|
|
182
213
|
|
|
183
214
|
with _db() as conn:
|
|
184
215
|
try:
|
|
185
216
|
conn.execute(
|
|
186
217
|
"DELETE FROM pinned_facts WHERE fact_id = ? AND profile_id = ?",
|
|
187
|
-
(
|
|
218
|
+
(body.fact_id, profile_id),
|
|
188
219
|
)
|
|
189
220
|
conn.commit()
|
|
221
|
+
authorization.complete()
|
|
190
222
|
return {"success": True, "unpinned": True}
|
|
191
223
|
except Exception as exc:
|
|
192
224
|
logger.error("unpin_fact failed: %s", exc, exc_info=True)
|
|
@@ -24,7 +24,11 @@ logger = logging.getLogger(__name__)
|
|
|
24
24
|
router = APIRouter(prefix="/api/v3/timeline", tags=["timeline"])
|
|
25
25
|
|
|
26
26
|
VALID_RANGES = {"1d", "7d", "30d", "90d", "365d"}
|
|
27
|
-
|
|
27
|
+
# ``date`` is the dashboard's chronological view. It does not need an extra
|
|
28
|
+
# enrichment pass because each returned event already carries its timestamp,
|
|
29
|
+
# but it is a first-class grouping selection and must not be rejected at the
|
|
30
|
+
# API boundary.
|
|
31
|
+
VALID_GROUP_BY = {"category", "community", "date"}
|
|
28
32
|
INTERNAL_CEILING = 5000
|
|
29
33
|
|
|
30
34
|
|