superlocalmemory 3.7.8 → 3.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/ATTRIBUTION.md +1 -3
- package/CHANGELOG.md +69 -0
- package/README.md +199 -29
- package/package.json +4 -2
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin/CLAUDE.md +8 -8
- package/plugin/agents/slm-governance-advisor.md +80 -0
- package/plugin/agents/slm-loop-runner.md +71 -0
- package/plugin/agents/slm-memory-advisor.md +10 -5
- package/plugin/agents/slm-optimize-advisor.md +9 -3
- package/plugin/commands/slm-loop.md +31 -0
- package/plugin/hooks/hooks.json +79 -0
- package/plugin/requirements.txt +1 -1
- package/plugin/scripts/slm-launch +46 -7
- package/plugin/settings.json +9 -0
- package/plugin/skills/slm-cache/SKILL.md +9 -1
- package/plugin/skills/slm-compress/SKILL.md +8 -1
- package/plugin/skills/slm-governance/SKILL.md +248 -0
- package/plugin/skills/slm-graph/SKILL.md +17 -3
- package/plugin/skills/slm-loop/SKILL.md +99 -0
- package/plugin/skills/slm-mesh/SKILL.md +282 -0
- package/plugin/skills/slm-profile/SKILL.md +148 -0
- package/plugin/skills/slm-recall/SKILL.md +46 -10
- package/plugin/skills/slm-remember/SKILL.md +48 -1
- package/plugin/skills/slm-scope/SKILL.md +176 -0
- package/plugin/skills/slm-session/SKILL.md +24 -1
- package/plugin/skills/slm-status/SKILL.md +18 -1
- package/plugin-src/agents/slm-governance-advisor.md +80 -0
- package/plugin-src/agents/slm-loop-runner.md +71 -0
- package/plugin-src/agents/slm-memory-advisor.md +10 -5
- package/plugin-src/agents/slm-optimize-advisor.md +9 -3
- package/plugin-src/commands/slm-loop.md +31 -0
- package/plugin-src/hooks/hooks.json +79 -0
- package/plugin-src/manifest.json +7 -2
- package/plugin-src/requirements.txt +1 -1
- package/plugin-src/rules/AGENTS.md +57 -18
- package/plugin-src/rules/CLAUDE.md.fragment +8 -8
- package/plugin-src/scripts/slm-launch +46 -7
- package/plugin-src/settings.json +9 -0
- package/plugin-src/skills/slm-cache/SKILL.md +9 -1
- package/plugin-src/skills/slm-compress/SKILL.md +8 -1
- package/plugin-src/skills/slm-governance/SKILL.md +248 -0
- package/plugin-src/skills/slm-graph/SKILL.md +17 -3
- package/plugin-src/skills/slm-loop/SKILL.md +99 -0
- package/plugin-src/skills/slm-mesh/SKILL.md +282 -0
- package/plugin-src/skills/slm-profile/SKILL.md +148 -0
- package/plugin-src/skills/slm-recall/SKILL.md +46 -10
- package/plugin-src/skills/slm-remember/SKILL.md +48 -1
- package/plugin-src/skills/slm-scope/SKILL.md +176 -0
- package/plugin-src/skills/slm-session/SKILL.md +24 -1
- package/plugin-src/skills/slm-status/SKILL.md +18 -1
- package/pyproject.toml +1 -1
- package/scripts/postinstall/validation.js +2 -0
- package/scripts/postinstall-interactive.js +74 -2
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/access/__init__.py +3 -0
- package/src/superlocalmemory/access/rbac.py +477 -0
- package/src/superlocalmemory/cli/commands.py +94 -10
- package/src/superlocalmemory/cli/compress_cmd.py +17 -7
- package/src/superlocalmemory/cli/loop_cmd.py +192 -0
- package/src/superlocalmemory/cli/main.py +39 -4
- package/src/superlocalmemory/cli/mesh_cmd.py +38 -0
- package/src/superlocalmemory/cli/optimize_cmd.py +3 -0
- package/src/superlocalmemory/cli/pending_store.py +49 -13
- package/src/superlocalmemory/cli/proxy_cmd.py +4 -0
- package/src/superlocalmemory/cli/scale_engine_cmd.py +6 -0
- package/src/superlocalmemory/cli/setup_wizard.py +22 -13
- package/src/superlocalmemory/compliance/audit.py +6 -0
- package/src/superlocalmemory/compliance/gdpr.py +128 -138
- package/src/superlocalmemory/compliance/retention.py +176 -45
- package/src/superlocalmemory/core/backend_orchestrator.py +5 -43
- package/src/superlocalmemory/core/community_summary.py +267 -0
- package/src/superlocalmemory/core/config.py +216 -3
- package/src/superlocalmemory/core/consolidation_engine.py +95 -22
- package/src/superlocalmemory/core/context_cache.py +61 -18
- package/src/superlocalmemory/core/embedding_worker.py +17 -2
- package/src/superlocalmemory/core/embeddings.py +12 -1
- package/src/superlocalmemory/core/engine.py +17 -1
- package/src/superlocalmemory/core/engine_ingestion.py +29 -0
- package/src/superlocalmemory/core/engine_wiring.py +13 -0
- package/src/superlocalmemory/core/entity_community.py +178 -0
- package/src/superlocalmemory/core/graph_analyzer.py +39 -2
- package/src/superlocalmemory/core/graph_pruner.py +13 -8
- package/src/superlocalmemory/core/key_expander.py +138 -0
- package/src/superlocalmemory/core/maintenance.py +23 -0
- package/src/superlocalmemory/core/modes.py +1 -1
- package/src/superlocalmemory/core/mutations.py +2 -2
- package/src/superlocalmemory/core/pii.py +105 -0
- package/src/superlocalmemory/core/progressive_abstraction.py +208 -0
- package/src/superlocalmemory/core/recall_pipeline.py +2 -0
- package/src/superlocalmemory/core/recall_worker.py +20 -6
- package/src/superlocalmemory/core/scale_engine.py +60 -1
- package/src/superlocalmemory/core/security_primitives.py +40 -2
- package/src/superlocalmemory/core/store_pipeline.py +35 -11
- package/src/superlocalmemory/core/worker_pool.py +21 -6
- package/src/superlocalmemory/encoding/entity_reflexion.py +200 -0
- package/src/superlocalmemory/encoding/entity_resolver.py +34 -24
- package/src/superlocalmemory/encoding/fact_extractor.py +26 -1
- package/src/superlocalmemory/encoding/temporal_validator.py +64 -1
- package/src/superlocalmemory/evolution/evolution_store.py +122 -45
- package/src/superlocalmemory/evolution/llm_dispatch.py +12 -1
- package/src/superlocalmemory/evolution/model_selection.py +160 -0
- package/src/superlocalmemory/evolution/mutation_generator.py +16 -0
- package/src/superlocalmemory/evolution/skill_evolver.py +127 -42
- package/src/superlocalmemory/evolution/triggers.py +22 -13
- package/src/superlocalmemory/graph/cozo_backend.py +43 -20
- package/src/superlocalmemory/hooks/adapter_base.py +5 -1
- package/src/superlocalmemory/hooks/auto_recall.py +13 -1
- package/src/superlocalmemory/hooks/claude_code_hooks.py +11 -0
- package/src/superlocalmemory/hooks/codex_assets.py +64 -5
- package/src/superlocalmemory/hooks/hook_daemon.py +20 -3
- package/src/superlocalmemory/hooks/memory_protocol.py +54 -0
- package/src/superlocalmemory/hooks/portable_kit.py +114 -1
- package/src/superlocalmemory/infra/backup.py +12 -1
- package/src/superlocalmemory/infra/daemon_identity.py +40 -4
- package/src/superlocalmemory/infra/data_root.py +43 -4
- package/src/superlocalmemory/infra/event_bus.py +107 -24
- package/src/superlocalmemory/infra/rate_limiter.py +93 -0
- package/src/superlocalmemory/ingestion/adapter_manager.py +4 -1
- package/src/superlocalmemory/ingestion/credentials.py +1 -1
- package/src/superlocalmemory/learning/cross_project.py +28 -19
- package/src/superlocalmemory/learning/reward_proxy.py +42 -9
- package/src/superlocalmemory/loops/__init__.py +56 -0
- package/src/superlocalmemory/loops/budget.py +58 -0
- package/src/superlocalmemory/loops/engine.py +164 -0
- package/src/superlocalmemory/loops/ledger.py +243 -0
- package/src/superlocalmemory/loops/models.py +152 -0
- package/src/superlocalmemory/loops/rules.py +52 -0
- package/src/superlocalmemory/mcp/_daemon_proxy.py +3 -0
- package/src/superlocalmemory/mcp/_pool_adapter.py +2 -0
- package/src/superlocalmemory/mcp/profiles.py +103 -0
- package/src/superlocalmemory/mcp/server.py +21 -49
- package/src/superlocalmemory/mcp/tools_active.py +4 -7
- package/src/superlocalmemory/mcp/tools_code_graph.py +51 -5
- package/src/superlocalmemory/mcp/tools_core.py +8 -1
- package/src/superlocalmemory/mcp/tools_evolution.py +6 -3
- package/src/superlocalmemory/mcp/tools_loops.py +300 -0
- package/src/superlocalmemory/mcp/tools_mesh.py +140 -4
- package/src/superlocalmemory/mcp/tools_optimize.py +15 -8
- package/src/superlocalmemory/mesh/broker.py +237 -129
- package/src/superlocalmemory/mesh/remote_sync.py +50 -8
- package/src/superlocalmemory/optimize/NOTICE +1 -6
- package/src/superlocalmemory/optimize/adapters/anthropic_adapter.py +1 -4
- package/src/superlocalmemory/optimize/adapters/openai_adapter.py +1 -4
- package/src/superlocalmemory/optimize/cache/semantic.py +27 -19
- package/src/superlocalmemory/optimize/compress/align.py +32 -26
- package/src/superlocalmemory/optimize/compress/ccr.py +14 -71
- package/src/superlocalmemory/optimize/compress/router.py +105 -22
- package/src/superlocalmemory/optimize/config/defaults.py +1 -1
- package/src/superlocalmemory/optimize/config/schema.py +87 -4
- package/src/superlocalmemory/optimize/metrics/counters.py +13 -4
- package/src/superlocalmemory/optimize/metrics/estimator.py +0 -3
- package/src/superlocalmemory/optimize/proxy/_helpers.py +31 -4
- package/src/superlocalmemory/optimize/storage/db.py +38 -9
- package/src/superlocalmemory/optimize/storage/schema.py +10 -0
- package/src/superlocalmemory/parameterization/pattern_extractor.py +6 -3
- package/src/superlocalmemory/retrieval/agentic.py +1 -1
- package/src/superlocalmemory/retrieval/bm25_channel.py +68 -10
- package/src/superlocalmemory/retrieval/engine.py +168 -26
- package/src/superlocalmemory/retrieval/entity_channel.py +7 -5
- package/src/superlocalmemory/retrieval/hopfield_channel.py +9 -2
- package/src/superlocalmemory/retrieval/semantic_channel.py +114 -21
- package/src/superlocalmemory/retrieval/spreading_activation.py +11 -2
- package/src/superlocalmemory/retrieval/temporal_channel.py +48 -9
- package/src/superlocalmemory/retrieval/temporal_frame.py +102 -0
- package/src/superlocalmemory/retrieval/temporal_validity_filter.py +135 -0
- package/src/superlocalmemory/retrieval/time_window.py +181 -0
- package/src/superlocalmemory/server/api.py +4 -4
- package/src/superlocalmemory/server/profile_runtime.py +125 -8
- package/src/superlocalmemory/server/rbac_enforce.py +142 -0
- package/src/superlocalmemory/server/recall_health.py +24 -3
- package/src/superlocalmemory/server/recall_serializer.py +19 -1
- package/src/superlocalmemory/server/routes/abstraction.py +115 -0
- package/src/superlocalmemory/server/routes/agents.py +128 -38
- package/src/superlocalmemory/server/routes/backup.py +34 -10
- package/src/superlocalmemory/server/routes/behavioral.py +13 -12
- package/src/superlocalmemory/server/routes/brain.py +21 -5
- package/src/superlocalmemory/server/routes/chat.py +10 -5
- package/src/superlocalmemory/server/routes/compliance.py +171 -21
- package/src/superlocalmemory/server/routes/config_api.py +436 -0
- package/src/superlocalmemory/server/routes/data_io.py +30 -8
- package/src/superlocalmemory/server/routes/entity.py +9 -4
- package/src/superlocalmemory/server/routes/events.py +24 -8
- package/src/superlocalmemory/server/routes/evolution.py +135 -17
- package/src/superlocalmemory/server/routes/helpers.py +16 -1
- package/src/superlocalmemory/server/routes/ingest.py +7 -4
- package/src/superlocalmemory/server/routes/insights.py +3 -3
- package/src/superlocalmemory/server/routes/learning.py +14 -14
- package/src/superlocalmemory/server/routes/lifecycle.py +59 -8
- package/src/superlocalmemory/server/routes/memories.py +182 -57
- package/src/superlocalmemory/server/routes/mesh.py +95 -15
- package/src/superlocalmemory/server/routes/optimize.py +33 -1
- package/src/superlocalmemory/server/routes/prewarm.py +2 -0
- package/src/superlocalmemory/server/routes/profiles.py +63 -17
- package/src/superlocalmemory/server/routes/ratelimit.py +124 -0
- package/src/superlocalmemory/server/routes/rbac.py +367 -0
- package/src/superlocalmemory/server/routes/stats.py +13 -6
- package/src/superlocalmemory/server/routes/tiers.py +11 -9
- package/src/superlocalmemory/server/routes/v3_api.py +183 -69
- package/src/superlocalmemory/server/routes/ws.py +5 -2
- package/src/superlocalmemory/server/security_middleware.py +12 -5
- package/src/superlocalmemory/server/ui.py +20 -5
- package/src/superlocalmemory/server/unified_daemon.py +384 -56
- package/src/superlocalmemory/server/write_identity.py +38 -8
- package/src/superlocalmemory/storage/database.py +265 -53
- package/src/superlocalmemory/storage/migration_runner.py +53 -0
- package/src/superlocalmemory/storage/migrations/M021_ingestion_log_profile.py +108 -0
- package/src/superlocalmemory/storage/migrations/M022_entity_aliases_profile.py +86 -0
- package/src/superlocalmemory/storage/migrations/M023_mesh_profile_isolation.py +194 -0
- package/src/superlocalmemory/storage/migrations/M024_rbac_users_roles.py +87 -0
- package/src/superlocalmemory/storage/migrations/M025_perf_indexes.py +90 -0
- package/src/superlocalmemory/storage/migrations/M026_rbac_memberships_fk.py +136 -0
- package/src/superlocalmemory/storage/migrations/M027_transferable_patterns_profile.py +163 -0
- package/src/superlocalmemory/storage/models.py +4 -0
- package/src/superlocalmemory/storage/schema.py +87 -0
- package/src/superlocalmemory/storage/schema_v343.py +24 -12
- package/src/superlocalmemory/trust/gate.py +49 -8
- package/src/superlocalmemory/ui/assets/slm-icon-white.svg +64 -0
- package/src/superlocalmemory/ui/assets/slm-icon.svg +36 -0
- package/src/superlocalmemory/ui/css/design-system.css +621 -0
- package/src/superlocalmemory/ui/css/neural-glass.css +6 -0
- package/src/superlocalmemory/ui/css/od-bridge.css +158 -0
- package/src/superlocalmemory/ui/favicon.svg +35 -4
- package/src/superlocalmemory/ui/index.html +306 -173
- package/src/superlocalmemory/ui/js/brain.js +5 -20
- package/src/superlocalmemory/ui/js/core.js +47 -31
- package/src/superlocalmemory/ui/js/dashboard.js +314 -63
- package/src/superlocalmemory/ui/js/event-delegation.js +102 -0
- package/src/superlocalmemory/ui/js/knowledge-graph.js +11 -11
- package/src/superlocalmemory/ui/js/math-health.js +1 -1
- package/src/superlocalmemory/ui/js/memories.js +15 -4
- package/src/superlocalmemory/ui/js/memory-chat.js +7 -7
- package/src/superlocalmemory/ui/js/ng-entities.js +6 -8
- package/src/superlocalmemory/ui/js/ng-ingestion.js +4 -4
- package/src/superlocalmemory/ui/js/ng-mesh.js +4 -9
- package/src/superlocalmemory/ui/js/ng-shell.js +8 -8
- package/src/superlocalmemory/ui/js/ng-skills.js +54 -2
- package/src/superlocalmemory/ui/js/od-agents.js +544 -0
- package/src/superlocalmemory/ui/js/od-auth-gate.js +257 -0
- package/src/superlocalmemory/ui/js/od-backup.js +780 -0
- package/src/superlocalmemory/ui/js/od-brain.js +779 -0
- package/src/superlocalmemory/ui/js/od-entities.js +579 -0
- package/src/superlocalmemory/ui/js/od-graph.js +593 -0
- package/src/superlocalmemory/ui/js/od-health.js +539 -0
- package/src/superlocalmemory/ui/js/od-mcp.js +508 -0
- package/src/superlocalmemory/ui/js/od-memories.js +887 -0
- package/src/superlocalmemory/ui/js/od-mesh.js +539 -0
- package/src/superlocalmemory/ui/js/od-operations.js +1250 -0
- package/src/superlocalmemory/ui/js/od-optimize.js +787 -0
- package/src/superlocalmemory/ui/js/od-settings.js +1053 -0
- package/src/superlocalmemory/ui/js/od-shell.js +593 -0
- package/src/superlocalmemory/ui/js/od-skills.js +573 -0
- package/src/superlocalmemory/ui/js/od-team.js +258 -0
- package/src/superlocalmemory/ui/js/profiles.js +159 -46
- package/src/superlocalmemory/ui/js/settings.js +2 -2
- package/src/superlocalmemory/ui/js/timeline.js +34 -5
- package/src/superlocalmemory/ui/js/trust-dashboard.js +2 -2
- package/src/superlocalmemory/vector/lancedb_backend.py +8 -6
- package/src/superlocalmemory/learning/behavioral_listener.py +0 -94
|
@@ -19,6 +19,12 @@ from .helpers import DB_PATH
|
|
|
19
19
|
logger = logging.getLogger("superlocalmemory.routes.agents")
|
|
20
20
|
router = APIRouter()
|
|
21
21
|
|
|
22
|
+
|
|
23
|
+
def _internal_error(detail: str = "Internal server error") -> HTTPException:
|
|
24
|
+
"""SEC-H-02: log full traceback server-side; return a generic message to the client."""
|
|
25
|
+
logger.exception("agents route error")
|
|
26
|
+
return HTTPException(status_code=500, detail=detail)
|
|
27
|
+
|
|
22
28
|
# Feature flag: V3 trust scorer
|
|
23
29
|
TRUST_AVAILABLE = False
|
|
24
30
|
try:
|
|
@@ -56,8 +62,8 @@ async def get_agents(
|
|
|
56
62
|
"count": len(agents),
|
|
57
63
|
"stats": {"total_agents": len(agents)},
|
|
58
64
|
}
|
|
59
|
-
except Exception
|
|
60
|
-
raise
|
|
65
|
+
except Exception:
|
|
66
|
+
raise _internal_error("Agent registry error")
|
|
61
67
|
|
|
62
68
|
|
|
63
69
|
@router.get("/api/agents/stats")
|
|
@@ -69,8 +75,91 @@ async def get_agent_stats(request: Request):
|
|
|
69
75
|
registry = AgentRegistry(persist_path=_registry_path())
|
|
70
76
|
agents = registry.list_agents()
|
|
71
77
|
return {"total_agents": len(agents)}
|
|
72
|
-
except Exception
|
|
73
|
-
raise
|
|
78
|
+
except Exception:
|
|
79
|
+
raise _internal_error("Agent stats error")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@router.get("/api/agents/memory-activity")
|
|
83
|
+
async def get_agent_memory_activity(
|
|
84
|
+
request: Request,
|
|
85
|
+
limit: int = Query(20, ge=1, le=100),
|
|
86
|
+
):
|
|
87
|
+
"""Per-agent memory attribution for the multi-agent memory view.
|
|
88
|
+
|
|
89
|
+
Reports how many memories each writing agent contributed, when each was
|
|
90
|
+
last active, which ingestion sources they used, and the most recent
|
|
91
|
+
entries — grouped by ``ingestion_operations.trusted_actor_id`` (the agent
|
|
92
|
+
that wrote the memory). Profile-scoped. Uses a direct DB read because the
|
|
93
|
+
dashboard runs without the engine subprocess. Never raises to the client;
|
|
94
|
+
returns empty structures if the operations table is absent.
|
|
95
|
+
"""
|
|
96
|
+
import sqlite3
|
|
97
|
+
|
|
98
|
+
from .helpers import get_active_profile
|
|
99
|
+
|
|
100
|
+
pid = get_active_profile()
|
|
101
|
+
agents: list[dict] = []
|
|
102
|
+
recent: list[dict] = []
|
|
103
|
+
total = 0
|
|
104
|
+
|
|
105
|
+
if DB_PATH.exists():
|
|
106
|
+
conn = sqlite3.connect(str(DB_PATH))
|
|
107
|
+
conn.row_factory = sqlite3.Row
|
|
108
|
+
try:
|
|
109
|
+
try:
|
|
110
|
+
rows = conn.execute(
|
|
111
|
+
"SELECT CASE WHEN trusted_actor_id='' THEN 'unknown' "
|
|
112
|
+
"ELSE trusted_actor_id END AS agent_id, "
|
|
113
|
+
"COUNT(*) AS cnt, MAX(created_at) AS last_active, "
|
|
114
|
+
"GROUP_CONCAT(DISTINCT source_type) AS sources "
|
|
115
|
+
"FROM ingestion_operations WHERE profile_id=? "
|
|
116
|
+
"GROUP BY agent_id ORDER BY cnt DESC, agent_id ASC "
|
|
117
|
+
"LIMIT 500",
|
|
118
|
+
(pid,),
|
|
119
|
+
).fetchall()
|
|
120
|
+
for r in rows:
|
|
121
|
+
agents.append({
|
|
122
|
+
"agent_id": r["agent_id"],
|
|
123
|
+
"count": r["cnt"],
|
|
124
|
+
"last_active": r["last_active"],
|
|
125
|
+
"source_types": (
|
|
126
|
+
[s for s in (r["sources"] or "").split(",") if s]
|
|
127
|
+
),
|
|
128
|
+
})
|
|
129
|
+
total += r["cnt"]
|
|
130
|
+
except sqlite3.OperationalError:
|
|
131
|
+
pass
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
rows = conn.execute(
|
|
135
|
+
"SELECT CASE WHEN trusted_actor_id='' THEN 'unknown' "
|
|
136
|
+
"ELSE trusted_actor_id END AS agent_id, "
|
|
137
|
+
"substr(raw_content, 1, 160) AS snippet, "
|
|
138
|
+
"created_at, source_type, session_id "
|
|
139
|
+
"FROM ingestion_operations WHERE profile_id=? "
|
|
140
|
+
"ORDER BY created_at DESC, rowid DESC LIMIT ?",
|
|
141
|
+
(pid, int(limit)),
|
|
142
|
+
).fetchall()
|
|
143
|
+
recent = [{
|
|
144
|
+
"agent_id": r["agent_id"],
|
|
145
|
+
"content": r["snippet"],
|
|
146
|
+
"created_at": r["created_at"],
|
|
147
|
+
"source_type": r["source_type"],
|
|
148
|
+
"session_id": r["session_id"],
|
|
149
|
+
} for r in rows]
|
|
150
|
+
except sqlite3.OperationalError:
|
|
151
|
+
pass
|
|
152
|
+
finally:
|
|
153
|
+
conn.close()
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
"ok": True,
|
|
157
|
+
"profile_id": pid,
|
|
158
|
+
"total_memories": total,
|
|
159
|
+
"agent_count": len(agents),
|
|
160
|
+
"agents": agents,
|
|
161
|
+
"recent": recent,
|
|
162
|
+
}
|
|
74
163
|
|
|
75
164
|
|
|
76
165
|
@router.get("/api/trust/stats")
|
|
@@ -102,38 +191,39 @@ async def get_trust_stats(request: Request):
|
|
|
102
191
|
conn = sqlite3.connect(str(DB_PATH))
|
|
103
192
|
conn.row_factory = sqlite3.Row
|
|
104
193
|
try:
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
try:
|
|
115
|
-
# Average trust score
|
|
116
|
-
row = conn.execute(
|
|
117
|
-
"SELECT AVG(trust_score) AS avg_ts FROM trust_scores "
|
|
118
|
-
"WHERE profile_id = ?", (pid,),
|
|
119
|
-
).fetchone()
|
|
120
|
-
if row and row["avg_ts"] is not None:
|
|
121
|
-
avg_trust_score = round(float(row["avg_ts"]), 3)
|
|
122
|
-
except sqlite3.OperationalError:
|
|
123
|
-
pass
|
|
194
|
+
try:
|
|
195
|
+
# Count trust signals
|
|
196
|
+
row = conn.execute(
|
|
197
|
+
"SELECT COUNT(*) AS cnt FROM trust_signals "
|
|
198
|
+
"WHERE profile_id = ?", (pid,),
|
|
199
|
+
).fetchone()
|
|
200
|
+
total_signals = row["cnt"] if row else 0
|
|
201
|
+
except sqlite3.OperationalError:
|
|
202
|
+
pass
|
|
124
203
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
204
|
+
try:
|
|
205
|
+
# Average trust score
|
|
206
|
+
row = conn.execute(
|
|
207
|
+
"SELECT AVG(trust_score) AS avg_ts FROM trust_scores "
|
|
208
|
+
"WHERE profile_id = ?", (pid,),
|
|
209
|
+
).fetchone()
|
|
210
|
+
if row and row["avg_ts"] is not None:
|
|
211
|
+
avg_trust_score = round(float(row["avg_ts"]), 3)
|
|
212
|
+
except sqlite3.OperationalError:
|
|
213
|
+
pass
|
|
135
214
|
|
|
136
|
-
|
|
215
|
+
try:
|
|
216
|
+
# Signal breakdown by type
|
|
217
|
+
rows = conn.execute(
|
|
218
|
+
"SELECT signal_type, COUNT(*) AS cnt "
|
|
219
|
+
"FROM trust_signals WHERE profile_id = ? "
|
|
220
|
+
"GROUP BY signal_type", (pid,),
|
|
221
|
+
).fetchall()
|
|
222
|
+
by_signal_type = {r["signal_type"]: r["cnt"] for r in rows}
|
|
223
|
+
except sqlite3.OperationalError:
|
|
224
|
+
pass
|
|
225
|
+
finally:
|
|
226
|
+
conn.close()
|
|
137
227
|
|
|
138
228
|
# Enforcement status: SLM uses "Silent Collection" by default
|
|
139
229
|
enforcement = "Silent Collection"
|
|
@@ -144,8 +234,8 @@ async def get_trust_stats(request: Request):
|
|
|
144
234
|
"enforcement": enforcement,
|
|
145
235
|
"by_signal_type": by_signal_type,
|
|
146
236
|
}
|
|
147
|
-
except Exception
|
|
148
|
-
raise
|
|
237
|
+
except Exception:
|
|
238
|
+
raise _internal_error("Trust stats error")
|
|
149
239
|
|
|
150
240
|
|
|
151
241
|
@router.get("/api/trust/signals/{agent_id}")
|
|
@@ -167,5 +257,5 @@ async def get_agent_trust_signals(
|
|
|
167
257
|
"signals": signals, "count": len(signals),
|
|
168
258
|
}
|
|
169
259
|
return {"agent_id": agent_id, "signals": [], "count": 0}
|
|
170
|
-
except Exception
|
|
171
|
-
raise
|
|
260
|
+
except Exception:
|
|
261
|
+
raise _internal_error("Trust signals error")
|
|
@@ -24,6 +24,13 @@ from .helpers import BackupConfigRequest, DB_PATH, MEMORY_DIR
|
|
|
24
24
|
logger = logging.getLogger("superlocalmemory.routes.backup")
|
|
25
25
|
router = APIRouter()
|
|
26
26
|
|
|
27
|
+
|
|
28
|
+
def _internal_error(detail: str = "Internal server error") -> HTTPException:
|
|
29
|
+
"""SEC-H-02: log full traceback server-side; return a generic message to the client."""
|
|
30
|
+
logger.exception("backup route error")
|
|
31
|
+
return HTTPException(status_code=500, detail=detail)
|
|
32
|
+
|
|
33
|
+
|
|
27
34
|
# Feature flags
|
|
28
35
|
BACKUP_AVAILABLE = False
|
|
29
36
|
CLOUD_AVAILABLE = False
|
|
@@ -79,8 +86,8 @@ async def backup_status():
|
|
|
79
86
|
else:
|
|
80
87
|
status["cloud_destinations"] = []
|
|
81
88
|
return status
|
|
82
|
-
except Exception
|
|
83
|
-
raise
|
|
89
|
+
except Exception:
|
|
90
|
+
raise _internal_error("Backup status error")
|
|
84
91
|
|
|
85
92
|
|
|
86
93
|
@router.post("/api/backup/create")
|
|
@@ -98,8 +105,8 @@ async def backup_create():
|
|
|
98
105
|
"status": manager.get_status(),
|
|
99
106
|
}
|
|
100
107
|
return {"success": False, "message": "Backup failed"}
|
|
101
|
-
except Exception
|
|
102
|
-
raise
|
|
108
|
+
except Exception:
|
|
109
|
+
raise _internal_error("Backup create error")
|
|
103
110
|
|
|
104
111
|
|
|
105
112
|
@router.post("/api/backup/configure")
|
|
@@ -115,21 +122,30 @@ async def backup_configure(request: BackupConfigRequest):
|
|
|
115
122
|
enabled=request.enabled,
|
|
116
123
|
)
|
|
117
124
|
return {"success": True, "message": "Backup configuration updated", "status": result}
|
|
118
|
-
except Exception
|
|
119
|
-
raise
|
|
125
|
+
except Exception:
|
|
126
|
+
raise _internal_error("Backup configure error")
|
|
120
127
|
|
|
121
128
|
|
|
122
129
|
@router.get("/api/backup/list")
|
|
123
|
-
async def backup_list():
|
|
130
|
+
async def backup_list(request: Request):
|
|
124
131
|
"""List all available backups."""
|
|
125
132
|
if not BACKUP_AVAILABLE:
|
|
126
133
|
return {"backups": [], "count": 0, "message": "Backup module not available"}
|
|
134
|
+
# This GET is not covered by the mutation middleware. The local machine
|
|
135
|
+
# owner (loopback) is trusted; a remote caller must present a credential;
|
|
136
|
+
# non-loopback uncredentialed callers fail closed. Using the mutation-actor
|
|
137
|
+
# boundary (not require_write_actor) so the same-origin dashboard — whose
|
|
138
|
+
# fetch wrapper only attaches the install token to mutating requests — can
|
|
139
|
+
# still read its own backup list.
|
|
140
|
+
from superlocalmemory.server.write_identity import require_http_mutation_actor
|
|
141
|
+
require_http_mutation_actor(request, getattr(request.app.state, "daemon_descriptor", None),
|
|
142
|
+
actor_kind="backup-list")
|
|
127
143
|
try:
|
|
128
144
|
manager = _get_backup_manager()
|
|
129
145
|
backups = manager.list_backups()
|
|
130
146
|
return {"backups": backups, "count": len(backups)}
|
|
131
|
-
except Exception
|
|
132
|
-
raise
|
|
147
|
+
except Exception:
|
|
148
|
+
raise _internal_error("Backup list error")
|
|
133
149
|
|
|
134
150
|
|
|
135
151
|
# ---- Cloud destination routes (v3.4.10) -----------------------------------
|
|
@@ -228,10 +244,18 @@ async def sync_cloud():
|
|
|
228
244
|
# ---- Export / Download route (v3.4.10) ------------------------------------
|
|
229
245
|
|
|
230
246
|
@router.get("/api/backup/export")
|
|
231
|
-
async def export_backup():
|
|
247
|
+
async def export_backup(request: Request):
|
|
232
248
|
"""Create and download a compressed backup archive."""
|
|
233
249
|
if not BACKUP_AVAILABLE:
|
|
234
250
|
raise HTTPException(status_code=501, detail="Backup module not available")
|
|
251
|
+
# Full-database download. This GET is not covered by the mutation
|
|
252
|
+
# middleware and is triggered by a top-level navigation (window.location),
|
|
253
|
+
# which cannot carry a custom credential header — so it uses the
|
|
254
|
+
# loopback-trusted mutation-actor boundary: the local owner may export,
|
|
255
|
+
# a non-loopback caller without a credential fails closed.
|
|
256
|
+
from superlocalmemory.server.write_identity import require_http_mutation_actor
|
|
257
|
+
require_http_mutation_actor(request, getattr(request.app.state, "daemon_descriptor", None),
|
|
258
|
+
actor_kind="backup-export")
|
|
235
259
|
|
|
236
260
|
manager = _get_backup_manager()
|
|
237
261
|
filename = manager.create_backup(label="export")
|
|
@@ -90,8 +90,8 @@ async def behavioral_status():
|
|
|
90
90
|
},
|
|
91
91
|
}
|
|
92
92
|
except Exception as e:
|
|
93
|
-
logger.
|
|
94
|
-
return {"available": False, "error":
|
|
93
|
+
logger.exception("behavioral_status error")
|
|
94
|
+
return {"available": False, "error": "Internal server error"}
|
|
95
95
|
|
|
96
96
|
|
|
97
97
|
@router.post("/api/behavioral/report-outcome")
|
|
@@ -173,8 +173,8 @@ async def report_outcome(data: dict):
|
|
|
173
173
|
),
|
|
174
174
|
}
|
|
175
175
|
except Exception as e:
|
|
176
|
-
logger.
|
|
177
|
-
return {"success": False, "error":
|
|
176
|
+
logger.exception("report_outcome error")
|
|
177
|
+
return {"success": False, "error": "Internal server error"}
|
|
178
178
|
|
|
179
179
|
|
|
180
180
|
# --------------------------------------------------------------------------
|
|
@@ -214,8 +214,8 @@ async def get_assertions(min_confidence: float = 0.0, category: str = "", limit:
|
|
|
214
214
|
"active_profile": profile,
|
|
215
215
|
}
|
|
216
216
|
except Exception as e:
|
|
217
|
-
logger.
|
|
218
|
-
return {"assertions": [], "count": 0, "error":
|
|
217
|
+
logger.exception("get_assertions error")
|
|
218
|
+
return {"assertions": [], "count": 0, "error": "Internal server error"}
|
|
219
219
|
|
|
220
220
|
|
|
221
221
|
@router.get("/api/behavioral/tool-events")
|
|
@@ -247,8 +247,8 @@ async def get_tool_events(tool_name: str = "", limit: int = 100):
|
|
|
247
247
|
finally:
|
|
248
248
|
conn.close()
|
|
249
249
|
except Exception as e:
|
|
250
|
-
logger.
|
|
251
|
-
return {"events": [], "count": 0, "error":
|
|
250
|
+
logger.exception("get_tool_events error")
|
|
251
|
+
return {"events": [], "count": 0, "error": "Internal server error"}
|
|
252
252
|
|
|
253
253
|
|
|
254
254
|
@router.get("/api/behavioral/soft-prompts")
|
|
@@ -269,8 +269,8 @@ async def get_soft_prompts():
|
|
|
269
269
|
"token_count", "active", "version", "created_at"], r
|
|
270
270
|
)) for r in rows], "count": len(rows)}
|
|
271
271
|
except Exception as e:
|
|
272
|
-
logger.
|
|
273
|
-
return {"prompts": [], "count": 0, "error":
|
|
272
|
+
logger.exception("get_soft_prompts error")
|
|
273
|
+
return {"prompts": [], "count": 0, "error": "Internal server error"}
|
|
274
274
|
|
|
275
275
|
|
|
276
276
|
@router.post("/api/v3/tool-event")
|
|
@@ -322,5 +322,6 @@ async def log_tool_event_api(data: dict):
|
|
|
322
322
|
finally:
|
|
323
323
|
conn.close()
|
|
324
324
|
return {"ok": True}
|
|
325
|
-
except Exception
|
|
326
|
-
|
|
325
|
+
except Exception:
|
|
326
|
+
logger.exception("behavioral route error")
|
|
327
|
+
return {"ok": False, "error": "Internal server error"}
|
|
@@ -30,6 +30,14 @@ Design notes (LLD-04 §7 hard rules):
|
|
|
30
30
|
never spell those names anywhere in this module.
|
|
31
31
|
* **U6** — install token required on ``/api/v3/brain`` and all
|
|
32
32
|
deprecated shim routes. See ``require_install_token`` below.
|
|
33
|
+
|
|
34
|
+
External API consumers (L1): the dashboard auto-fetches the token from
|
|
35
|
+
``GET /internal/token`` (loopback + same-origin only) and sends it as the
|
|
36
|
+
``X-Install-Token`` header. Non-browser callers (scripts, IDE adapters,
|
|
37
|
+
third-party integrations) instead read the token from
|
|
38
|
+
``~/.superlocalmemory/.install_token`` and send the same header:
|
|
39
|
+
``curl -H "X-Install-Token: $(cat ~/.superlocalmemory/.install_token)" \
|
|
40
|
+
http://127.0.0.1:8765/api/v3/brain``.
|
|
33
41
|
* **U10** — feature count surfaced from ``features.FEATURE_DIM``;
|
|
34
42
|
stratum total from module constant ``_STRATA_TOTAL`` (48 = 4×3×4).
|
|
35
43
|
"""
|
|
@@ -52,6 +60,7 @@ from superlocalmemory.core.security_primitives import (
|
|
|
52
60
|
from superlocalmemory.learning.database import LearningDatabase
|
|
53
61
|
from superlocalmemory.learning.features import FEATURE_DIM
|
|
54
62
|
from superlocalmemory.infra.data_root import canonical_data_root
|
|
63
|
+
from .helpers import get_active_profile
|
|
55
64
|
|
|
56
65
|
logger = logging.getLogger("superlocalmemory.routes.brain")
|
|
57
66
|
|
|
@@ -900,7 +909,7 @@ def _action_outcomes_count(lrn_db: LearningDatabase,
|
|
|
900
909
|
|
|
901
910
|
|
|
902
911
|
@router.get("/brain", dependencies=[Depends(require_install_token)])
|
|
903
|
-
async def get_brain(profile_id: str =
|
|
912
|
+
async def get_brain(profile_id: str | None = None) -> dict:
|
|
904
913
|
"""Unified Brain endpoint — LLD-04 §3.1.
|
|
905
914
|
|
|
906
915
|
Fan-out: each section is a synchronous SQLite reader. Running them
|
|
@@ -915,6 +924,9 @@ async def get_brain(profile_id: str = "default") -> dict:
|
|
|
915
924
|
"""
|
|
916
925
|
import asyncio
|
|
917
926
|
|
|
927
|
+
# Default to the ACTIVE profile (request runtime truth), never literal
|
|
928
|
+
# "default" — the Brain must reflect whichever profile is active.
|
|
929
|
+
profile_id = profile_id or get_active_profile()
|
|
918
930
|
lrn_db = LearningDatabase(_learning_db_path())
|
|
919
931
|
|
|
920
932
|
(
|
|
@@ -1176,7 +1188,7 @@ def _compute_evolution_cost_preview(profile_id: str) -> dict:
|
|
|
1176
1188
|
@router.get("/brain/evolution-timeseries",
|
|
1177
1189
|
dependencies=[Depends(require_install_token)])
|
|
1178
1190
|
async def get_brain_evolution_timeseries(
|
|
1179
|
-
profile_id: str =
|
|
1191
|
+
profile_id: str | None = None,
|
|
1180
1192
|
days: int = _EVOLUTION_DEFAULT_DAYS,
|
|
1181
1193
|
) -> dict:
|
|
1182
1194
|
"""Daily learning-signal counts for ``profile_id`` over the last ``days``.
|
|
@@ -1186,6 +1198,7 @@ async def get_brain_evolution_timeseries(
|
|
|
1186
1198
|
"""
|
|
1187
1199
|
import asyncio
|
|
1188
1200
|
|
|
1201
|
+
profile_id = profile_id or get_active_profile()
|
|
1189
1202
|
lrn_db = LearningDatabase(_learning_db_path())
|
|
1190
1203
|
result = await asyncio.to_thread(
|
|
1191
1204
|
_compute_evolution_timeseries, profile_id, lrn_db, days=days,
|
|
@@ -1201,7 +1214,8 @@ async def get_brain_evolution_timeseries(
|
|
|
1201
1214
|
|
|
1202
1215
|
@router.get("/learning/stats",
|
|
1203
1216
|
dependencies=[Depends(require_install_token)])
|
|
1204
|
-
async def learning_stats_deprecated(profile_id: str =
|
|
1217
|
+
async def learning_stats_deprecated(profile_id: str | None = None) -> dict:
|
|
1218
|
+
profile_id = profile_id or get_active_profile()
|
|
1205
1219
|
lrn_db = LearningDatabase(_learning_db_path())
|
|
1206
1220
|
return {
|
|
1207
1221
|
"deprecated": True,
|
|
@@ -1212,7 +1226,8 @@ async def learning_stats_deprecated(profile_id: str = "default") -> dict:
|
|
|
1212
1226
|
|
|
1213
1227
|
@router.get("/patterns",
|
|
1214
1228
|
dependencies=[Depends(require_install_token)])
|
|
1215
|
-
async def patterns_deprecated(profile_id: str =
|
|
1229
|
+
async def patterns_deprecated(profile_id: str | None = None) -> dict:
|
|
1230
|
+
profile_id = profile_id or get_active_profile()
|
|
1216
1231
|
return {
|
|
1217
1232
|
"deprecated": True,
|
|
1218
1233
|
"use_instead": "/api/v3/brain",
|
|
@@ -1222,7 +1237,8 @@ async def patterns_deprecated(profile_id: str = "default") -> dict:
|
|
|
1222
1237
|
|
|
1223
1238
|
@router.get("/behavioral",
|
|
1224
1239
|
dependencies=[Depends(require_install_token)])
|
|
1225
|
-
async def behavioral_deprecated(profile_id: str =
|
|
1240
|
+
async def behavioral_deprecated(profile_id: str | None = None) -> dict:
|
|
1241
|
+
profile_id = profile_id or get_active_profile()
|
|
1226
1242
|
return {
|
|
1227
1243
|
"deprecated": True,
|
|
1228
1244
|
"use_instead": "/api/v3/brain",
|
|
@@ -101,8 +101,9 @@ async def _stream_chat(
|
|
|
101
101
|
memories = await loop.run_in_executor(
|
|
102
102
|
None, _recall_memories, app_state, query, limit,
|
|
103
103
|
)
|
|
104
|
-
except Exception
|
|
105
|
-
|
|
104
|
+
except Exception:
|
|
105
|
+
logger.exception("chat: memory retrieval failed")
|
|
106
|
+
yield _sse_event("error", json.dumps({"message": "Memory retrieval failed"}))
|
|
106
107
|
yield _sse_event("done", "")
|
|
107
108
|
return
|
|
108
109
|
|
|
@@ -226,8 +227,9 @@ async def _stream_mode_bc(
|
|
|
226
227
|
yield _sse_event("token", token)
|
|
227
228
|
except httpx.ConnectError:
|
|
228
229
|
yield _sse_event("token", f"\n\n[Connection failed — is {provider} running?]")
|
|
229
|
-
except Exception
|
|
230
|
-
|
|
230
|
+
except Exception:
|
|
231
|
+
logger.exception("chat: LLM streaming error")
|
|
232
|
+
yield _sse_event("token", "\n\n[LLM error: request failed]")
|
|
231
233
|
|
|
232
234
|
|
|
233
235
|
# ── Ollama Streaming (/api/chat with messages) ───────────────────
|
|
@@ -359,7 +361,10 @@ def _recall_via_resident_engine(app_state, query: str, limit: int) -> list:
|
|
|
359
361
|
if r.fact.memory_id
|
|
360
362
|
})
|
|
361
363
|
memory_map = (
|
|
362
|
-
engine._db.get_memory_content_batch(
|
|
364
|
+
engine._db.get_memory_content_batch(
|
|
365
|
+
memory_ids, engine.profile_id,
|
|
366
|
+
include_global=True, include_shared=True,
|
|
367
|
+
) if memory_ids else {}
|
|
363
368
|
)
|
|
364
369
|
from superlocalmemory.server.recall_serializer import (
|
|
365
370
|
serialize_recall_response,
|