superlocalmemory 3.7.8 → 3.8.1
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 +129 -0
- package/README.md +205 -123
- package/package.json +12 -3
- package/plugin/.claude-plugin/plugin.json +2 -3
- 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/rules/AGENTS.md +57 -18
- 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-graph/SKILL.md +17 -3
- 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-session/SKILL.md +24 -1
- package/plugin-src/skills/slm-status/SKILL.md +18 -1
- package/pyproject.toml +2 -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 +228 -17
- package/src/superlocalmemory/cli/compress_cmd.py +17 -7
- package/src/superlocalmemory/cli/daemon.py +7 -0
- package/src/superlocalmemory/cli/loop_cmd.py +187 -0
- package/src/superlocalmemory/cli/main.py +49 -8
- 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/cli/version_banner.py +17 -3
- 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 +23 -59
- 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 +21 -7
- package/src/superlocalmemory/core/embeddings.py +131 -46
- package/src/superlocalmemory/core/engine.py +41 -22
- package/src/superlocalmemory/core/engine_ingestion.py +359 -43
- 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/ingestion_command.py +134 -25
- package/src/superlocalmemory/core/injection.py +12 -7
- package/src/superlocalmemory/core/key_expander.py +138 -0
- package/src/superlocalmemory/core/maintenance.py +23 -0
- package/src/superlocalmemory/core/maintenance_scheduler.py +17 -7
- 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 +7 -3
- 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 +186 -29
- 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/hook_handlers.py +6 -1
- package/src/superlocalmemory/hooks/memory_protocol.py +54 -0
- package/src/superlocalmemory/hooks/portable_kit.py +148 -3
- 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/model_rollback.py +3 -0
- package/src/superlocalmemory/learning/ranker_retrain_online.py +2 -0
- package/src/superlocalmemory/learning/reward.py +50 -0
- package/src/superlocalmemory/learning/reward_proxy.py +42 -9
- package/src/superlocalmemory/learning/source_quality.py +523 -1
- 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 +263 -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 +32 -79
- 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 +12 -4
- package/src/superlocalmemory/mcp/tools_evolution.py +6 -3
- package/src/superlocalmemory/mcp/tools_learning.py +2 -2
- 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 +221 -47
- package/src/superlocalmemory/retrieval/entity_channel.py +7 -5
- package/src/superlocalmemory/retrieval/hopfield_channel.py +9 -2
- package/src/superlocalmemory/retrieval/reranker.py +3 -4
- 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/config_file.py +90 -0
- package/src/superlocalmemory/server/origin.py +50 -0
- 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 +317 -70
- package/src/superlocalmemory/server/routes/behavioral.py +349 -71
- package/src/superlocalmemory/server/routes/brain.py +69 -12
- 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 +438 -0
- package/src/superlocalmemory/server/routes/data_io.py +30 -8
- package/src/superlocalmemory/server/routes/entity.py +108 -26
- package/src/superlocalmemory/server/routes/events.py +24 -8
- package/src/superlocalmemory/server/routes/evolution.py +189 -68
- 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 +289 -118
- package/src/superlocalmemory/server/routes/learning_telemetry.py +153 -0
- 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 +200 -31
- 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 +132 -0
- package/src/superlocalmemory/server/routes/rbac.py +367 -0
- package/src/superlocalmemory/server/routes/stats.py +103 -158
- package/src/superlocalmemory/server/routes/tiers.py +11 -9
- package/src/superlocalmemory/server/routes/token.py +3 -13
- package/src/superlocalmemory/server/routes/v3_api.py +247 -89
- 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 +827 -72
- package/src/superlocalmemory/server/write_identity.py +38 -8
- package/src/superlocalmemory/storage/database.py +265 -53
- package/src/superlocalmemory/storage/migration_runner.py +132 -1
- package/src/superlocalmemory/storage/migrations/M010_evolution_config.py +5 -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/migrations/M028_fact_entity_associations.py +270 -0
- package/src/superlocalmemory/storage/migrations/M029_behavioral_history_indexes.py +137 -0
- package/src/superlocalmemory/storage/migrations/M030_entity_explorer_indexes.py +93 -0
- package/src/superlocalmemory/storage/migrations/__init__.py +4 -0
- package/src/superlocalmemory/storage/models.py +4 -0
- package/src/superlocalmemory/storage/schema.py +136 -1
- package/src/superlocalmemory/storage/schema_v32.py +2 -0
- package/src/superlocalmemory/storage/schema_v343.py +24 -12
- package/src/superlocalmemory/storage/schema_v347.py +4 -0
- 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 +303 -173
- package/src/superlocalmemory/ui/js/brain.js +5 -20
- package/src/superlocalmemory/ui/js/core.js +100 -41
- package/src/superlocalmemory/ui/js/dashboard.js +403 -65
- 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 +871 -0
- package/src/superlocalmemory/ui/js/od-brain.js +816 -0
- package/src/superlocalmemory/ui/js/od-entities.js +579 -0
- package/src/superlocalmemory/ui/js/od-graph.js +600 -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 +929 -0
- package/src/superlocalmemory/ui/js/od-mesh.js +553 -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 +1107 -0
- package/src/superlocalmemory/ui/js/od-shell.js +809 -0
- package/src/superlocalmemory/ui/js/od-skills.js +600 -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 +17 -3
- 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/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 -23
- package/plugin-src/scripts/slm-launch.bat +0 -23
- package/plugin-src/settings.json +0 -16
- package/src/superlocalmemory/learning/behavioral_listener.py +0 -94
|
@@ -4,23 +4,86 @@
|
|
|
4
4
|
"""SuperLocalMemory V3 - Stats Routes
|
|
5
5
|
- AGPL-3.0-or-later
|
|
6
6
|
|
|
7
|
-
Routes: /api/stats, /api/timeline
|
|
7
|
+
Routes: /api/stats, /api/timeline
|
|
8
8
|
"""
|
|
9
|
-
import json
|
|
10
9
|
import logging
|
|
11
|
-
|
|
10
|
+
import sqlite3
|
|
12
11
|
from typing import Optional
|
|
13
12
|
|
|
14
13
|
from fastapi import APIRouter, HTTPException, Query
|
|
15
14
|
|
|
16
|
-
from .helpers import get_db_connection, dict_factory, get_active_profile, DB_PATH
|
|
15
|
+
from .helpers import get_db_connection, dict_factory, get_active_profile, DB_PATH
|
|
17
16
|
|
|
18
17
|
logger = logging.getLogger("superlocalmemory.routes.stats")
|
|
19
18
|
router = APIRouter()
|
|
20
19
|
|
|
21
20
|
|
|
21
|
+
def _query_ingestion_sources(cursor, profile_id: str) -> list[dict]:
|
|
22
|
+
"""Query profile-scoped ingestion provenance without double-counting facts."""
|
|
23
|
+
cursor.execute(
|
|
24
|
+
"""
|
|
25
|
+
SELECT COALESCE(NULLIF(TRIM(source_type), ''), 'unknown') AS source_type,
|
|
26
|
+
COUNT(DISTINCT fact_id) AS count
|
|
27
|
+
FROM provenance
|
|
28
|
+
WHERE profile_id = ?
|
|
29
|
+
GROUP BY COALESCE(NULLIF(TRIM(source_type), ''), 'unknown')
|
|
30
|
+
ORDER BY count DESC, source_type ASC
|
|
31
|
+
""",
|
|
32
|
+
(profile_id,),
|
|
33
|
+
)
|
|
34
|
+
return [dict(row) for row in cursor.fetchall()]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _load_ingestion_sources_with_status(
|
|
38
|
+
cursor, profile_id: str,
|
|
39
|
+
) -> tuple[list[dict], dict]:
|
|
40
|
+
"""Return provenance plus a truthful availability state.
|
|
41
|
+
|
|
42
|
+
Older databases may not have the provenance table. In that case the
|
|
43
|
+
dashboard must report an empty/unknown state rather than inventing a split.
|
|
44
|
+
Lock or I/O failures are distinct from a legitimate legacy-empty state.
|
|
45
|
+
"""
|
|
46
|
+
try:
|
|
47
|
+
return _query_ingestion_sources(cursor, profile_id), {
|
|
48
|
+
"available": True,
|
|
49
|
+
"state": "recorded",
|
|
50
|
+
"source": "memory.db:provenance",
|
|
51
|
+
}
|
|
52
|
+
except sqlite3.OperationalError as exc:
|
|
53
|
+
if "no such table" in str(exc).lower():
|
|
54
|
+
return [], {
|
|
55
|
+
"available": True,
|
|
56
|
+
"state": "legacy_not_recorded",
|
|
57
|
+
"source": "memory.db:provenance",
|
|
58
|
+
}
|
|
59
|
+
logger.warning("provenance metrics temporarily unavailable: %s", exc)
|
|
60
|
+
return [], {
|
|
61
|
+
"available": False,
|
|
62
|
+
"state": "temporarily_unavailable",
|
|
63
|
+
"source": "memory.db:provenance",
|
|
64
|
+
}
|
|
65
|
+
except sqlite3.Error as exc:
|
|
66
|
+
logger.warning("provenance metrics unavailable: %s", exc)
|
|
67
|
+
return [], {
|
|
68
|
+
"available": False,
|
|
69
|
+
"state": "temporarily_unavailable",
|
|
70
|
+
"source": "memory.db:provenance",
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _load_ingestion_sources(cursor, profile_id: str) -> list[dict]:
|
|
75
|
+
"""Compatibility helper returning only the provenance rows."""
|
|
76
|
+
return _load_ingestion_sources_with_status(cursor, profile_id)[0]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _internal_error(detail: str = "Internal server error") -> HTTPException:
|
|
80
|
+
"""SEC-H-02: log full traceback server-side; return a generic message to the client."""
|
|
81
|
+
logger.exception("stats route error")
|
|
82
|
+
return HTTPException(status_code=500, detail=detail)
|
|
83
|
+
|
|
84
|
+
|
|
22
85
|
@router.get("/api/stats")
|
|
23
|
-
|
|
86
|
+
def get_stats():
|
|
24
87
|
"""Get comprehensive system statistics."""
|
|
25
88
|
try:
|
|
26
89
|
conn = get_db_connection()
|
|
@@ -199,6 +262,9 @@ async def get_stats():
|
|
|
199
262
|
importance_dist = cursor.fetchall()
|
|
200
263
|
|
|
201
264
|
db_size = DB_PATH.stat().st_size if DB_PATH.exists() else 0
|
|
265
|
+
ingestion_sources, ingestion_sources_status = (
|
|
266
|
+
_load_ingestion_sources_with_status(cursor, active_profile)
|
|
267
|
+
)
|
|
202
268
|
|
|
203
269
|
if total_graph_nodes > 1:
|
|
204
270
|
max_edges = (total_graph_nodes * (total_graph_nodes - 1)) / 2
|
|
@@ -235,18 +301,21 @@ async def get_stats():
|
|
|
235
301
|
if total_graph_nodes > 0 else 0
|
|
236
302
|
),
|
|
237
303
|
},
|
|
304
|
+
"ingestion_sources": ingestion_sources,
|
|
305
|
+
"ingestion_sources_status": ingestion_sources_status,
|
|
238
306
|
}
|
|
239
307
|
|
|
240
|
-
except Exception
|
|
241
|
-
raise
|
|
308
|
+
except Exception:
|
|
309
|
+
raise _internal_error("Stats error")
|
|
242
310
|
|
|
243
311
|
|
|
244
312
|
@router.get("/api/timeline")
|
|
245
|
-
|
|
313
|
+
def get_timeline(
|
|
246
314
|
days: int = Query(30, ge=1, le=365),
|
|
247
315
|
group_by: str = Query("day", pattern="^(day|week|month)$"),
|
|
316
|
+
include_categories: bool = Query(True),
|
|
248
317
|
):
|
|
249
|
-
"""Get temporal
|
|
318
|
+
"""Get temporal memory creation; dashboard callers may skip category scans."""
|
|
250
319
|
try:
|
|
251
320
|
conn = get_db_connection()
|
|
252
321
|
conn.row_factory = dict_factory
|
|
@@ -280,22 +349,29 @@ async def get_timeline(
|
|
|
280
349
|
""", (days, active_profile))
|
|
281
350
|
timeline = cursor.fetchall()
|
|
282
351
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
352
|
+
if include_categories:
|
|
353
|
+
cursor.execute(f"""
|
|
354
|
+
SELECT {date_group} as period, {cat_col} as category, COUNT(*) as count
|
|
355
|
+
FROM {table}
|
|
356
|
+
WHERE created_at >= datetime('now', '-' || ? || ' days')
|
|
357
|
+
AND {cat_col} IS NOT NULL AND {profile_col} = ?
|
|
358
|
+
GROUP BY {date_group}, {cat_col} ORDER BY period DESC, count DESC
|
|
359
|
+
""", (days, active_profile))
|
|
360
|
+
category_trend = cursor.fetchall()
|
|
361
|
+
|
|
362
|
+
cursor.execute(f"""
|
|
363
|
+
SELECT COUNT(*) as total_memories,
|
|
364
|
+
COUNT(DISTINCT {cat_col}) as categories_used
|
|
365
|
+
FROM {table}
|
|
366
|
+
WHERE created_at >= datetime('now', '-' || ? || ' days') AND {profile_col} = ?
|
|
367
|
+
""", (days, active_profile))
|
|
368
|
+
period_stats = cursor.fetchone()
|
|
369
|
+
else:
|
|
370
|
+
category_trend = []
|
|
371
|
+
period_stats = {
|
|
372
|
+
"total_memories": sum(int(row["count"] or 0) for row in timeline),
|
|
373
|
+
"categories_used": None,
|
|
374
|
+
}
|
|
299
375
|
|
|
300
376
|
conn.close()
|
|
301
377
|
|
|
@@ -305,136 +381,5 @@ async def get_timeline(
|
|
|
305
381
|
"parameters": {"days": days, "group_by": group_by},
|
|
306
382
|
}
|
|
307
383
|
|
|
308
|
-
except Exception
|
|
309
|
-
raise
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
@router.get("/api/patterns")
|
|
313
|
-
async def get_patterns():
|
|
314
|
-
"""Get learned patterns."""
|
|
315
|
-
try:
|
|
316
|
-
conn = get_db_connection()
|
|
317
|
-
conn.row_factory = dict_factory
|
|
318
|
-
cursor = conn.cursor()
|
|
319
|
-
active_profile = get_active_profile()
|
|
320
|
-
|
|
321
|
-
# Check for V3 learning tables or V2 identity_patterns
|
|
322
|
-
patterns = []
|
|
323
|
-
table_name = None
|
|
324
|
-
for candidate in ('learned_patterns', 'identity_patterns'):
|
|
325
|
-
cursor.execute(
|
|
326
|
-
"SELECT name FROM sqlite_master WHERE type='table' AND name=?",
|
|
327
|
-
(candidate,),
|
|
328
|
-
)
|
|
329
|
-
if cursor.fetchone():
|
|
330
|
-
table_name = candidate
|
|
331
|
-
break
|
|
332
|
-
|
|
333
|
-
if not table_name:
|
|
334
|
-
conn.close()
|
|
335
|
-
# Fall through to V3.1 behavioral pattern store
|
|
336
|
-
try:
|
|
337
|
-
from superlocalmemory.learning.behavioral import BehavioralPatternStore
|
|
338
|
-
store = BehavioralPatternStore(str(MEMORY_DIR / "learning.db"))
|
|
339
|
-
raw = store.get_patterns(profile_id=active_profile)
|
|
340
|
-
# v3.4.7: Map all pattern types to frontend categories
|
|
341
|
-
type_map = {
|
|
342
|
-
"tech_preference": "preference",
|
|
343
|
-
"interest": "preference",
|
|
344
|
-
"entity_preferences": "preference",
|
|
345
|
-
"style": "style",
|
|
346
|
-
"fact_type_distribution": "style",
|
|
347
|
-
"knowledge_structure": "style",
|
|
348
|
-
"terminology": "terminology",
|
|
349
|
-
"temporal": "workflow",
|
|
350
|
-
"session_activity": "workflow",
|
|
351
|
-
"workflow": "workflow",
|
|
352
|
-
"co_retrieval_clusters": "workflow",
|
|
353
|
-
"channel_performance": "performance",
|
|
354
|
-
}
|
|
355
|
-
grouped = defaultdict(list)
|
|
356
|
-
for p in raw:
|
|
357
|
-
meta = p.get("metadata", {})
|
|
358
|
-
data = meta # metadata IS the data dict
|
|
359
|
-
frontend_key = type_map.get(p.get("pattern_type", ""), "preference")
|
|
360
|
-
# Extract human-readable value from data fields
|
|
361
|
-
readable_value = (
|
|
362
|
-
data.get("value")
|
|
363
|
-
or data.get("topic")
|
|
364
|
-
or data.get("pattern_key", "")
|
|
365
|
-
or p.get("pattern_key", "")
|
|
366
|
-
)
|
|
367
|
-
readable_key = (
|
|
368
|
-
data.get("pattern_key")
|
|
369
|
-
or data.get("key")
|
|
370
|
-
or p.get("pattern_key", "")
|
|
371
|
-
)
|
|
372
|
-
grouped[frontend_key].append({
|
|
373
|
-
"pattern_type": p.get("pattern_type", ""),
|
|
374
|
-
"key": readable_key,
|
|
375
|
-
"value": readable_value,
|
|
376
|
-
"confidence": p.get("confidence", 0),
|
|
377
|
-
"evidence_count": data.get("evidence", p.get("evidence_count", 0)),
|
|
378
|
-
})
|
|
379
|
-
all_patterns = [p for ps in grouped.values() for p in ps]
|
|
380
|
-
confs = [p["confidence"] for p in all_patterns if p.get("confidence")]
|
|
381
|
-
return {
|
|
382
|
-
"patterns": dict(grouped),
|
|
383
|
-
"total_patterns": len(all_patterns),
|
|
384
|
-
"pattern_types": list(grouped.keys()),
|
|
385
|
-
"confidence_stats": {
|
|
386
|
-
"avg": sum(confs) / len(confs) if confs else 0,
|
|
387
|
-
"min": min(confs) if confs else 0,
|
|
388
|
-
"max": max(confs) if confs else 0,
|
|
389
|
-
},
|
|
390
|
-
}
|
|
391
|
-
except Exception:
|
|
392
|
-
return {
|
|
393
|
-
"patterns": {}, "total_patterns": 0, "pattern_types": [],
|
|
394
|
-
"message": "Pattern learning not initialized.",
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
if table_name == 'identity_patterns':
|
|
398
|
-
cursor.execute("""
|
|
399
|
-
SELECT pattern_type, key, value, confidence, evidence_count,
|
|
400
|
-
updated_at as last_updated
|
|
401
|
-
FROM identity_patterns WHERE profile = ?
|
|
402
|
-
ORDER BY confidence DESC, evidence_count DESC
|
|
403
|
-
""", (active_profile,))
|
|
404
|
-
else:
|
|
405
|
-
cursor.execute("""
|
|
406
|
-
SELECT pattern_type, key, value, confidence, evidence_count,
|
|
407
|
-
last_updated
|
|
408
|
-
FROM learned_patterns WHERE is_active = 1
|
|
409
|
-
ORDER BY confidence DESC, evidence_count DESC
|
|
410
|
-
""")
|
|
411
|
-
|
|
412
|
-
patterns = cursor.fetchall()
|
|
413
|
-
|
|
414
|
-
for pattern in patterns:
|
|
415
|
-
if pattern.get('value'):
|
|
416
|
-
try:
|
|
417
|
-
pattern['value'] = json.loads(pattern['value'])
|
|
418
|
-
except Exception:
|
|
419
|
-
pass
|
|
420
|
-
|
|
421
|
-
grouped = defaultdict(list)
|
|
422
|
-
for pattern in patterns:
|
|
423
|
-
grouped[pattern['pattern_type']].append(pattern)
|
|
424
|
-
|
|
425
|
-
confidences = [p['confidence'] for p in patterns if p.get('confidence')]
|
|
426
|
-
confidence_stats = {
|
|
427
|
-
"avg": sum(confidences) / len(confidences) if confidences else 0,
|
|
428
|
-
"min": min(confidences) if confidences else 0,
|
|
429
|
-
"max": max(confidences) if confidences else 0,
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
conn.close()
|
|
433
|
-
|
|
434
|
-
return {
|
|
435
|
-
"patterns": dict(grouped), "total_patterns": len(patterns),
|
|
436
|
-
"pattern_types": list(grouped.keys()), "confidence_stats": confidence_stats,
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
except Exception as e:
|
|
440
|
-
return {"patterns": {}, "total_patterns": 0, "error": str(e)}
|
|
384
|
+
except Exception:
|
|
385
|
+
raise _internal_error("Timeline error")
|
|
@@ -20,7 +20,7 @@ from pydantic import BaseModel, Field
|
|
|
20
20
|
|
|
21
21
|
from superlocalmemory.server.route_mutations import authorize_route_mutation
|
|
22
22
|
|
|
23
|
-
from .helpers import DB_PATH
|
|
23
|
+
from .helpers import DB_PATH, get_active_profile
|
|
24
24
|
|
|
25
25
|
logger = logging.getLogger("superlocalmemory.routes.tiers")
|
|
26
26
|
router = APIRouter()
|
|
@@ -55,9 +55,11 @@ def _validate_profile(profile_id: str) -> str:
|
|
|
55
55
|
|
|
56
56
|
|
|
57
57
|
@router.get("/api/tiers/stats")
|
|
58
|
-
async def tier_stats(profile_id: str =
|
|
58
|
+
async def tier_stats(profile_id: str | None = None):
|
|
59
59
|
"""Get tier distribution stats."""
|
|
60
|
-
|
|
60
|
+
# Default to the ACTIVE profile, never the literal "default" — otherwise
|
|
61
|
+
# tier counts reflect the default profile regardless of which is active.
|
|
62
|
+
profile_id = _validate_profile(profile_id or get_active_profile())
|
|
61
63
|
|
|
62
64
|
with _db() as conn:
|
|
63
65
|
try:
|
|
@@ -98,13 +100,13 @@ async def tier_stats(profile_id: str = "default"):
|
|
|
98
100
|
|
|
99
101
|
|
|
100
102
|
@router.post("/api/tiers/evaluate")
|
|
101
|
-
async def evaluate_tiers_route(request: Request, profile_id: str =
|
|
103
|
+
async def evaluate_tiers_route(request: Request, profile_id: str | None = None):
|
|
102
104
|
"""Manually trigger tier evaluation.
|
|
103
105
|
|
|
104
106
|
Uses the shared engine (via lazy import) instead of re-initializing
|
|
105
107
|
DatabaseManager on every request.
|
|
106
108
|
"""
|
|
107
|
-
profile_id = _validate_profile(profile_id)
|
|
109
|
+
profile_id = _validate_profile(profile_id or get_active_profile())
|
|
108
110
|
|
|
109
111
|
try:
|
|
110
112
|
authorization = authorize_route_mutation(
|
|
@@ -137,13 +139,13 @@ async def evaluate_tiers_route(request: Request, profile_id: str = "default"):
|
|
|
137
139
|
async def pin_fact_route(
|
|
138
140
|
body: PinRequest,
|
|
139
141
|
request: Request,
|
|
140
|
-
profile_id: str =
|
|
142
|
+
profile_id: str | None = None,
|
|
141
143
|
):
|
|
142
144
|
"""Pin a fact to stay in active tier forever.
|
|
143
145
|
|
|
144
146
|
Validates fact exists in the specified profile before pinning.
|
|
145
147
|
"""
|
|
146
|
-
profile_id = _validate_profile(profile_id)
|
|
148
|
+
profile_id = _validate_profile(profile_id or get_active_profile())
|
|
147
149
|
authorization = authorize_route_mutation(
|
|
148
150
|
request,
|
|
149
151
|
operation="update",
|
|
@@ -194,7 +196,7 @@ async def pin_fact_route(
|
|
|
194
196
|
async def unpin_fact_route(
|
|
195
197
|
body: PinRequest,
|
|
196
198
|
request: Request,
|
|
197
|
-
profile_id: str =
|
|
199
|
+
profile_id: str | None = None,
|
|
198
200
|
):
|
|
199
201
|
"""Unpin a fact, allowing normal tier demotion.
|
|
200
202
|
|
|
@@ -202,7 +204,7 @@ async def unpin_fact_route(
|
|
|
202
204
|
based on access patterns. This is intentional — immediate demotion would
|
|
203
205
|
surprise the user.
|
|
204
206
|
"""
|
|
205
|
-
profile_id = _validate_profile(profile_id)
|
|
207
|
+
profile_id = _validate_profile(profile_id or get_active_profile())
|
|
206
208
|
authorization = authorize_route_mutation(
|
|
207
209
|
request,
|
|
208
210
|
operation="update",
|
|
@@ -31,21 +31,11 @@ logger = logging.getLogger(__name__)
|
|
|
31
31
|
router = APIRouter(tags=["internal"])
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
_ALLOWED_ORIGIN_PREFIXES = (
|
|
35
|
-
"http://127.0.0.1",
|
|
36
|
-
"https://127.0.0.1",
|
|
37
|
-
"http://localhost",
|
|
38
|
-
"https://localhost",
|
|
39
|
-
"http://[::1]",
|
|
40
|
-
"https://[::1]",
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
|
|
44
34
|
def _origin_is_loopback(origin: str) -> bool:
|
|
45
35
|
"""Return True iff ``origin`` is absent or a loopback URL."""
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
return
|
|
36
|
+
from superlocalmemory.server.origin import origin_is_loopback
|
|
37
|
+
|
|
38
|
+
return origin_is_loopback(origin)
|
|
49
39
|
|
|
50
40
|
|
|
51
41
|
@router.get("/internal/token")
|