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.
Files changed (260) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/ATTRIBUTION.md +1 -3
  3. package/CHANGELOG.md +69 -0
  4. package/README.md +199 -29
  5. package/package.json +4 -2
  6. package/plugin/.claude-plugin/plugin.json +2 -2
  7. package/plugin/CLAUDE.md +8 -8
  8. package/plugin/agents/slm-governance-advisor.md +80 -0
  9. package/plugin/agents/slm-loop-runner.md +71 -0
  10. package/plugin/agents/slm-memory-advisor.md +10 -5
  11. package/plugin/agents/slm-optimize-advisor.md +9 -3
  12. package/plugin/commands/slm-loop.md +31 -0
  13. package/plugin/hooks/hooks.json +79 -0
  14. package/plugin/requirements.txt +1 -1
  15. package/plugin/scripts/slm-launch +46 -7
  16. package/plugin/settings.json +9 -0
  17. package/plugin/skills/slm-cache/SKILL.md +9 -1
  18. package/plugin/skills/slm-compress/SKILL.md +8 -1
  19. package/plugin/skills/slm-governance/SKILL.md +248 -0
  20. package/plugin/skills/slm-graph/SKILL.md +17 -3
  21. package/plugin/skills/slm-loop/SKILL.md +99 -0
  22. package/plugin/skills/slm-mesh/SKILL.md +282 -0
  23. package/plugin/skills/slm-profile/SKILL.md +148 -0
  24. package/plugin/skills/slm-recall/SKILL.md +46 -10
  25. package/plugin/skills/slm-remember/SKILL.md +48 -1
  26. package/plugin/skills/slm-scope/SKILL.md +176 -0
  27. package/plugin/skills/slm-session/SKILL.md +24 -1
  28. package/plugin/skills/slm-status/SKILL.md +18 -1
  29. package/plugin-src/agents/slm-governance-advisor.md +80 -0
  30. package/plugin-src/agents/slm-loop-runner.md +71 -0
  31. package/plugin-src/agents/slm-memory-advisor.md +10 -5
  32. package/plugin-src/agents/slm-optimize-advisor.md +9 -3
  33. package/plugin-src/commands/slm-loop.md +31 -0
  34. package/plugin-src/hooks/hooks.json +79 -0
  35. package/plugin-src/manifest.json +7 -2
  36. package/plugin-src/requirements.txt +1 -1
  37. package/plugin-src/rules/AGENTS.md +57 -18
  38. package/plugin-src/rules/CLAUDE.md.fragment +8 -8
  39. package/plugin-src/scripts/slm-launch +46 -7
  40. package/plugin-src/settings.json +9 -0
  41. package/plugin-src/skills/slm-cache/SKILL.md +9 -1
  42. package/plugin-src/skills/slm-compress/SKILL.md +8 -1
  43. package/plugin-src/skills/slm-governance/SKILL.md +248 -0
  44. package/plugin-src/skills/slm-graph/SKILL.md +17 -3
  45. package/plugin-src/skills/slm-loop/SKILL.md +99 -0
  46. package/plugin-src/skills/slm-mesh/SKILL.md +282 -0
  47. package/plugin-src/skills/slm-profile/SKILL.md +148 -0
  48. package/plugin-src/skills/slm-recall/SKILL.md +46 -10
  49. package/plugin-src/skills/slm-remember/SKILL.md +48 -1
  50. package/plugin-src/skills/slm-scope/SKILL.md +176 -0
  51. package/plugin-src/skills/slm-session/SKILL.md +24 -1
  52. package/plugin-src/skills/slm-status/SKILL.md +18 -1
  53. package/pyproject.toml +1 -1
  54. package/scripts/postinstall/validation.js +2 -0
  55. package/scripts/postinstall-interactive.js +74 -2
  56. package/src/superlocalmemory/__init__.py +1 -1
  57. package/src/superlocalmemory/access/__init__.py +3 -0
  58. package/src/superlocalmemory/access/rbac.py +477 -0
  59. package/src/superlocalmemory/cli/commands.py +94 -10
  60. package/src/superlocalmemory/cli/compress_cmd.py +17 -7
  61. package/src/superlocalmemory/cli/loop_cmd.py +192 -0
  62. package/src/superlocalmemory/cli/main.py +39 -4
  63. package/src/superlocalmemory/cli/mesh_cmd.py +38 -0
  64. package/src/superlocalmemory/cli/optimize_cmd.py +3 -0
  65. package/src/superlocalmemory/cli/pending_store.py +49 -13
  66. package/src/superlocalmemory/cli/proxy_cmd.py +4 -0
  67. package/src/superlocalmemory/cli/scale_engine_cmd.py +6 -0
  68. package/src/superlocalmemory/cli/setup_wizard.py +22 -13
  69. package/src/superlocalmemory/compliance/audit.py +6 -0
  70. package/src/superlocalmemory/compliance/gdpr.py +128 -138
  71. package/src/superlocalmemory/compliance/retention.py +176 -45
  72. package/src/superlocalmemory/core/backend_orchestrator.py +5 -43
  73. package/src/superlocalmemory/core/community_summary.py +267 -0
  74. package/src/superlocalmemory/core/config.py +216 -3
  75. package/src/superlocalmemory/core/consolidation_engine.py +95 -22
  76. package/src/superlocalmemory/core/context_cache.py +61 -18
  77. package/src/superlocalmemory/core/embedding_worker.py +17 -2
  78. package/src/superlocalmemory/core/embeddings.py +12 -1
  79. package/src/superlocalmemory/core/engine.py +17 -1
  80. package/src/superlocalmemory/core/engine_ingestion.py +29 -0
  81. package/src/superlocalmemory/core/engine_wiring.py +13 -0
  82. package/src/superlocalmemory/core/entity_community.py +178 -0
  83. package/src/superlocalmemory/core/graph_analyzer.py +39 -2
  84. package/src/superlocalmemory/core/graph_pruner.py +13 -8
  85. package/src/superlocalmemory/core/key_expander.py +138 -0
  86. package/src/superlocalmemory/core/maintenance.py +23 -0
  87. package/src/superlocalmemory/core/modes.py +1 -1
  88. package/src/superlocalmemory/core/mutations.py +2 -2
  89. package/src/superlocalmemory/core/pii.py +105 -0
  90. package/src/superlocalmemory/core/progressive_abstraction.py +208 -0
  91. package/src/superlocalmemory/core/recall_pipeline.py +2 -0
  92. package/src/superlocalmemory/core/recall_worker.py +20 -6
  93. package/src/superlocalmemory/core/scale_engine.py +60 -1
  94. package/src/superlocalmemory/core/security_primitives.py +40 -2
  95. package/src/superlocalmemory/core/store_pipeline.py +35 -11
  96. package/src/superlocalmemory/core/worker_pool.py +21 -6
  97. package/src/superlocalmemory/encoding/entity_reflexion.py +200 -0
  98. package/src/superlocalmemory/encoding/entity_resolver.py +34 -24
  99. package/src/superlocalmemory/encoding/fact_extractor.py +26 -1
  100. package/src/superlocalmemory/encoding/temporal_validator.py +64 -1
  101. package/src/superlocalmemory/evolution/evolution_store.py +122 -45
  102. package/src/superlocalmemory/evolution/llm_dispatch.py +12 -1
  103. package/src/superlocalmemory/evolution/model_selection.py +160 -0
  104. package/src/superlocalmemory/evolution/mutation_generator.py +16 -0
  105. package/src/superlocalmemory/evolution/skill_evolver.py +127 -42
  106. package/src/superlocalmemory/evolution/triggers.py +22 -13
  107. package/src/superlocalmemory/graph/cozo_backend.py +43 -20
  108. package/src/superlocalmemory/hooks/adapter_base.py +5 -1
  109. package/src/superlocalmemory/hooks/auto_recall.py +13 -1
  110. package/src/superlocalmemory/hooks/claude_code_hooks.py +11 -0
  111. package/src/superlocalmemory/hooks/codex_assets.py +64 -5
  112. package/src/superlocalmemory/hooks/hook_daemon.py +20 -3
  113. package/src/superlocalmemory/hooks/memory_protocol.py +54 -0
  114. package/src/superlocalmemory/hooks/portable_kit.py +114 -1
  115. package/src/superlocalmemory/infra/backup.py +12 -1
  116. package/src/superlocalmemory/infra/daemon_identity.py +40 -4
  117. package/src/superlocalmemory/infra/data_root.py +43 -4
  118. package/src/superlocalmemory/infra/event_bus.py +107 -24
  119. package/src/superlocalmemory/infra/rate_limiter.py +93 -0
  120. package/src/superlocalmemory/ingestion/adapter_manager.py +4 -1
  121. package/src/superlocalmemory/ingestion/credentials.py +1 -1
  122. package/src/superlocalmemory/learning/cross_project.py +28 -19
  123. package/src/superlocalmemory/learning/reward_proxy.py +42 -9
  124. package/src/superlocalmemory/loops/__init__.py +56 -0
  125. package/src/superlocalmemory/loops/budget.py +58 -0
  126. package/src/superlocalmemory/loops/engine.py +164 -0
  127. package/src/superlocalmemory/loops/ledger.py +243 -0
  128. package/src/superlocalmemory/loops/models.py +152 -0
  129. package/src/superlocalmemory/loops/rules.py +52 -0
  130. package/src/superlocalmemory/mcp/_daemon_proxy.py +3 -0
  131. package/src/superlocalmemory/mcp/_pool_adapter.py +2 -0
  132. package/src/superlocalmemory/mcp/profiles.py +103 -0
  133. package/src/superlocalmemory/mcp/server.py +21 -49
  134. package/src/superlocalmemory/mcp/tools_active.py +4 -7
  135. package/src/superlocalmemory/mcp/tools_code_graph.py +51 -5
  136. package/src/superlocalmemory/mcp/tools_core.py +8 -1
  137. package/src/superlocalmemory/mcp/tools_evolution.py +6 -3
  138. package/src/superlocalmemory/mcp/tools_loops.py +300 -0
  139. package/src/superlocalmemory/mcp/tools_mesh.py +140 -4
  140. package/src/superlocalmemory/mcp/tools_optimize.py +15 -8
  141. package/src/superlocalmemory/mesh/broker.py +237 -129
  142. package/src/superlocalmemory/mesh/remote_sync.py +50 -8
  143. package/src/superlocalmemory/optimize/NOTICE +1 -6
  144. package/src/superlocalmemory/optimize/adapters/anthropic_adapter.py +1 -4
  145. package/src/superlocalmemory/optimize/adapters/openai_adapter.py +1 -4
  146. package/src/superlocalmemory/optimize/cache/semantic.py +27 -19
  147. package/src/superlocalmemory/optimize/compress/align.py +32 -26
  148. package/src/superlocalmemory/optimize/compress/ccr.py +14 -71
  149. package/src/superlocalmemory/optimize/compress/router.py +105 -22
  150. package/src/superlocalmemory/optimize/config/defaults.py +1 -1
  151. package/src/superlocalmemory/optimize/config/schema.py +87 -4
  152. package/src/superlocalmemory/optimize/metrics/counters.py +13 -4
  153. package/src/superlocalmemory/optimize/metrics/estimator.py +0 -3
  154. package/src/superlocalmemory/optimize/proxy/_helpers.py +31 -4
  155. package/src/superlocalmemory/optimize/storage/db.py +38 -9
  156. package/src/superlocalmemory/optimize/storage/schema.py +10 -0
  157. package/src/superlocalmemory/parameterization/pattern_extractor.py +6 -3
  158. package/src/superlocalmemory/retrieval/agentic.py +1 -1
  159. package/src/superlocalmemory/retrieval/bm25_channel.py +68 -10
  160. package/src/superlocalmemory/retrieval/engine.py +168 -26
  161. package/src/superlocalmemory/retrieval/entity_channel.py +7 -5
  162. package/src/superlocalmemory/retrieval/hopfield_channel.py +9 -2
  163. package/src/superlocalmemory/retrieval/semantic_channel.py +114 -21
  164. package/src/superlocalmemory/retrieval/spreading_activation.py +11 -2
  165. package/src/superlocalmemory/retrieval/temporal_channel.py +48 -9
  166. package/src/superlocalmemory/retrieval/temporal_frame.py +102 -0
  167. package/src/superlocalmemory/retrieval/temporal_validity_filter.py +135 -0
  168. package/src/superlocalmemory/retrieval/time_window.py +181 -0
  169. package/src/superlocalmemory/server/api.py +4 -4
  170. package/src/superlocalmemory/server/profile_runtime.py +125 -8
  171. package/src/superlocalmemory/server/rbac_enforce.py +142 -0
  172. package/src/superlocalmemory/server/recall_health.py +24 -3
  173. package/src/superlocalmemory/server/recall_serializer.py +19 -1
  174. package/src/superlocalmemory/server/routes/abstraction.py +115 -0
  175. package/src/superlocalmemory/server/routes/agents.py +128 -38
  176. package/src/superlocalmemory/server/routes/backup.py +34 -10
  177. package/src/superlocalmemory/server/routes/behavioral.py +13 -12
  178. package/src/superlocalmemory/server/routes/brain.py +21 -5
  179. package/src/superlocalmemory/server/routes/chat.py +10 -5
  180. package/src/superlocalmemory/server/routes/compliance.py +171 -21
  181. package/src/superlocalmemory/server/routes/config_api.py +436 -0
  182. package/src/superlocalmemory/server/routes/data_io.py +30 -8
  183. package/src/superlocalmemory/server/routes/entity.py +9 -4
  184. package/src/superlocalmemory/server/routes/events.py +24 -8
  185. package/src/superlocalmemory/server/routes/evolution.py +135 -17
  186. package/src/superlocalmemory/server/routes/helpers.py +16 -1
  187. package/src/superlocalmemory/server/routes/ingest.py +7 -4
  188. package/src/superlocalmemory/server/routes/insights.py +3 -3
  189. package/src/superlocalmemory/server/routes/learning.py +14 -14
  190. package/src/superlocalmemory/server/routes/lifecycle.py +59 -8
  191. package/src/superlocalmemory/server/routes/memories.py +182 -57
  192. package/src/superlocalmemory/server/routes/mesh.py +95 -15
  193. package/src/superlocalmemory/server/routes/optimize.py +33 -1
  194. package/src/superlocalmemory/server/routes/prewarm.py +2 -0
  195. package/src/superlocalmemory/server/routes/profiles.py +63 -17
  196. package/src/superlocalmemory/server/routes/ratelimit.py +124 -0
  197. package/src/superlocalmemory/server/routes/rbac.py +367 -0
  198. package/src/superlocalmemory/server/routes/stats.py +13 -6
  199. package/src/superlocalmemory/server/routes/tiers.py +11 -9
  200. package/src/superlocalmemory/server/routes/v3_api.py +183 -69
  201. package/src/superlocalmemory/server/routes/ws.py +5 -2
  202. package/src/superlocalmemory/server/security_middleware.py +12 -5
  203. package/src/superlocalmemory/server/ui.py +20 -5
  204. package/src/superlocalmemory/server/unified_daemon.py +384 -56
  205. package/src/superlocalmemory/server/write_identity.py +38 -8
  206. package/src/superlocalmemory/storage/database.py +265 -53
  207. package/src/superlocalmemory/storage/migration_runner.py +53 -0
  208. package/src/superlocalmemory/storage/migrations/M021_ingestion_log_profile.py +108 -0
  209. package/src/superlocalmemory/storage/migrations/M022_entity_aliases_profile.py +86 -0
  210. package/src/superlocalmemory/storage/migrations/M023_mesh_profile_isolation.py +194 -0
  211. package/src/superlocalmemory/storage/migrations/M024_rbac_users_roles.py +87 -0
  212. package/src/superlocalmemory/storage/migrations/M025_perf_indexes.py +90 -0
  213. package/src/superlocalmemory/storage/migrations/M026_rbac_memberships_fk.py +136 -0
  214. package/src/superlocalmemory/storage/migrations/M027_transferable_patterns_profile.py +163 -0
  215. package/src/superlocalmemory/storage/models.py +4 -0
  216. package/src/superlocalmemory/storage/schema.py +87 -0
  217. package/src/superlocalmemory/storage/schema_v343.py +24 -12
  218. package/src/superlocalmemory/trust/gate.py +49 -8
  219. package/src/superlocalmemory/ui/assets/slm-icon-white.svg +64 -0
  220. package/src/superlocalmemory/ui/assets/slm-icon.svg +36 -0
  221. package/src/superlocalmemory/ui/css/design-system.css +621 -0
  222. package/src/superlocalmemory/ui/css/neural-glass.css +6 -0
  223. package/src/superlocalmemory/ui/css/od-bridge.css +158 -0
  224. package/src/superlocalmemory/ui/favicon.svg +35 -4
  225. package/src/superlocalmemory/ui/index.html +306 -173
  226. package/src/superlocalmemory/ui/js/brain.js +5 -20
  227. package/src/superlocalmemory/ui/js/core.js +47 -31
  228. package/src/superlocalmemory/ui/js/dashboard.js +314 -63
  229. package/src/superlocalmemory/ui/js/event-delegation.js +102 -0
  230. package/src/superlocalmemory/ui/js/knowledge-graph.js +11 -11
  231. package/src/superlocalmemory/ui/js/math-health.js +1 -1
  232. package/src/superlocalmemory/ui/js/memories.js +15 -4
  233. package/src/superlocalmemory/ui/js/memory-chat.js +7 -7
  234. package/src/superlocalmemory/ui/js/ng-entities.js +6 -8
  235. package/src/superlocalmemory/ui/js/ng-ingestion.js +4 -4
  236. package/src/superlocalmemory/ui/js/ng-mesh.js +4 -9
  237. package/src/superlocalmemory/ui/js/ng-shell.js +8 -8
  238. package/src/superlocalmemory/ui/js/ng-skills.js +54 -2
  239. package/src/superlocalmemory/ui/js/od-agents.js +544 -0
  240. package/src/superlocalmemory/ui/js/od-auth-gate.js +257 -0
  241. package/src/superlocalmemory/ui/js/od-backup.js +780 -0
  242. package/src/superlocalmemory/ui/js/od-brain.js +779 -0
  243. package/src/superlocalmemory/ui/js/od-entities.js +579 -0
  244. package/src/superlocalmemory/ui/js/od-graph.js +593 -0
  245. package/src/superlocalmemory/ui/js/od-health.js +539 -0
  246. package/src/superlocalmemory/ui/js/od-mcp.js +508 -0
  247. package/src/superlocalmemory/ui/js/od-memories.js +887 -0
  248. package/src/superlocalmemory/ui/js/od-mesh.js +539 -0
  249. package/src/superlocalmemory/ui/js/od-operations.js +1250 -0
  250. package/src/superlocalmemory/ui/js/od-optimize.js +787 -0
  251. package/src/superlocalmemory/ui/js/od-settings.js +1053 -0
  252. package/src/superlocalmemory/ui/js/od-shell.js +593 -0
  253. package/src/superlocalmemory/ui/js/od-skills.js +573 -0
  254. package/src/superlocalmemory/ui/js/od-team.js +258 -0
  255. package/src/superlocalmemory/ui/js/profiles.js +159 -46
  256. package/src/superlocalmemory/ui/js/settings.js +2 -2
  257. package/src/superlocalmemory/ui/js/timeline.js +34 -5
  258. package/src/superlocalmemory/ui/js/trust-dashboard.js +2 -2
  259. package/src/superlocalmemory/vector/lancedb_backend.py +8 -6
  260. package/src/superlocalmemory/learning/behavioral_listener.py +0 -94
@@ -0,0 +1,539 @@
1
+ // od-health.js — OD Health pane renderer v2 (design-faithful)
2
+ // Renders into window.odRenderHealth(container).
3
+ //
4
+ // Endpoints (confirmed with curl against daemon :8765):
5
+ // GET /health → {status, version, pid, state, readiness}
6
+ // GET /api/stats → {overview: {db_size_mb, total_memories, ...}}
7
+ // GET /api/v3/math/health → {health: {fisher, sheaf, langevin}, overall, note}
8
+ // GET /api/agents → {agents: [{agent_id, profile_id, registered_at}], count, stats}
9
+ // GET /api/trust/stats → {avg_trust_score, total_signals, enforcement, by_signal_type}
10
+ // GET /api/events/stats → {total_events, events_last_24h, "memory.stored", "memory.recalled", ...}
11
+ //
12
+ // JSON key mapping for events/stats: top-level dot-notation keys e.g. data["memory.stored"]
13
+ // JSON key mapping for agents: data.stats.total_agents, data.agents[].{agent_id, profile_id, registered_at}
14
+ //
15
+ // CRIT fixes applied:
16
+ // (a) No hardcoded numbers — all values from real API or '—' with TODO comment.
17
+ // (b) Agent table 6 columns: 2 real (Agent, Last seen), 4 with '—' (Protocol, Trust,
18
+ // Writes, Recalls — no per-agent endpoint exists).
19
+ // (c) KPI 4th card "Mesh broker" shows 'Unknown' — no /api/mesh/* endpoint confirmed.
20
+ //
21
+ // Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar — AGPL-3.0
22
+
23
+ /* global window, document, fetch, Promise */
24
+ (function () {
25
+ 'use strict';
26
+
27
+ // ─── Helpers ──────────────────────────────────────────────────────────────
28
+
29
+ /** HTML-escape all API strings before inserting via innerHTML. */
30
+ function esc(s) {
31
+ return String(s == null ? '' : s)
32
+ .replace(/&/g, '&')
33
+ .replace(/</g, '&lt;')
34
+ .replace(/>/g, '&gt;')
35
+ .replace(/"/g, '&quot;')
36
+ .replace(/'/g, '&#39;');
37
+ }
38
+
39
+ /**
40
+ * Format a Unix-seconds float or ISO string as "Xm ago".
41
+ * Returns '—' for falsy input.
42
+ */
43
+ function timeAgo(ts) {
44
+ if (!ts) return '—';
45
+ var ms = (typeof ts === 'number') ? ts * 1000 : new Date(ts).getTime();
46
+ var d = (Date.now() - ms) / 1000;
47
+ if (d < 0) d = 0;
48
+ if (d < 60) return Math.floor(d) + 's ago';
49
+ if (d < 3600) return Math.floor(d / 60) + 'm ago';
50
+ if (d < 86400) return Math.floor(d / 3600) + 'h ago';
51
+ return Math.floor(d / 86400) + 'd ago';
52
+ }
53
+
54
+ /** Format a number with locale separators; safe for null/undefined. */
55
+ function fmtNum(n) { return Number(n || 0).toLocaleString(); }
56
+
57
+ /**
58
+ * Map a status string to an OD badge/color class.
59
+ * Returns: 'ok' | 'warn' | 'danger' | 'neutral'
60
+ */
61
+ function statusClass(st) {
62
+ var s = String(st || '').toLowerCase();
63
+ if (s === 'ok' || s === 'healthy' || s === 'running' || s === 'active' ||
64
+ s === 'configured' || s === 'ready') return 'ok';
65
+ if (s === 'degraded' || s === 'stale' || s === 'warning' || s === 'warn') return 'warn';
66
+ if (s === 'error' || s === 'dead' || s === 'critical') return 'danger';
67
+ return 'neutral';
68
+ }
69
+
70
+ /** Return the CSS var color for a status class. */
71
+ function statusColor(cls) {
72
+ if (cls === 'ok') return 'var(--ok)';
73
+ if (cls === 'warn') return 'var(--warn)';
74
+ if (cls === 'danger') return 'var(--danger)';
75
+ return 'var(--fg-2)';
76
+ }
77
+
78
+ /** Get an inline SVG from the shell icon registry; safe if shell not loaded. */
79
+ function safeIcon(name) {
80
+ return (typeof window.slmIcon === 'function') ? window.slmIcon(name) : '';
81
+ }
82
+
83
+ // ─── Skeleton HTML ────────────────────────────────────────────────────────
84
+
85
+ /**
86
+ * Static structural HTML for the health pane — zero API data inside.
87
+ * All dynamic content is written by the populate* functions.
88
+ * IDs use od-h-* prefix to avoid colliding with legacy Bootstrap IDs.
89
+ */
90
+ function buildSkeleton() {
91
+ return (
92
+ // Page header
93
+ '<div class="page-head">' +
94
+ '<h2>System health</h2>' +
95
+ '<p>Math layers, the connected-agent stream and daemon status — ' +
96
+ 'click any card to drill down. All green means your memory is learning correctly.</p>' +
97
+ '</div>' +
98
+
99
+ // KPI strip: 4 status cards (Daemon, Memory DB, Math layers, Mesh broker)
100
+ '<section class="kpi-strip" style="margin-bottom:16px" id="od-h-status">' +
101
+ '<div class="card kpi"><div class="dim" style="font-size:12px;padding:20px">Loading status…</div></div>' +
102
+ '</section>' +
103
+
104
+ // Two-column: Math health + Trust signals
105
+ '<div class="grid" style="grid-template-columns:1fr 1fr;align-items:start">' +
106
+
107
+ '<div class="card">' +
108
+ '<div class="card-head">' +
109
+ '<h3>Math-layer health</h3>' +
110
+ '<span class="sub">Scoring layers · Consistency · Lifecycle</span>' +
111
+ '</div>' +
112
+ '<div class="card-pad" id="od-h-math">' +
113
+ '<div class="dim" style="text-align:center;padding:20px">Loading…</div>' +
114
+ '</div>' +
115
+ '</div>' +
116
+
117
+ '<div class="card">' +
118
+ '<div class="card-head">' +
119
+ '<h3>Trust signals</h3>' +
120
+ '<span class="sub" id="od-h-trust-mode">loading…</span>' +
121
+ '</div>' +
122
+ '<div class="card-pad">' +
123
+ '<div style="display:flex;gap:24px;margin-bottom:16px">' +
124
+ '<div>' +
125
+ '<div class="dim" style="font-size:11px">Avg trust score</div>' +
126
+ '<div class="num" style="font-size:24px;font-weight:700" id="od-h-trust-avg">—</div>' +
127
+ '</div>' +
128
+ '<div>' +
129
+ '<div class="dim" style="font-size:11px">Total signals</div>' +
130
+ '<div class="num" style="font-size:24px;font-weight:700" id="od-h-trust-total">—</div>' +
131
+ '</div>' +
132
+ '</div>' +
133
+ '<div id="od-h-signals"></div>' +
134
+ '</div>' +
135
+ '</div>' +
136
+
137
+ '</div>' +
138
+
139
+ // Connected agents card (full-width)
140
+ '<div class="card" style="margin-top:16px">' +
141
+ '<div class="card-head">' +
142
+ '<h3>Connected agents</h3>' +
143
+ '<span class="sub">live write / recall stream</span>' +
144
+ '<div class="spacer"></div>' +
145
+ // pulse class + explicit dot color as in design
146
+ '<span class="badge neutral pulse" style="border-radius:99px">' +
147
+ '<span class="dot" style="background:var(--ok)"></span> live' +
148
+ '</span>' +
149
+ '</div>' +
150
+ '<div class="card-pad">' +
151
+ // Summary stats row: 4 metrics
152
+ '<div style="display:flex;gap:24px;margin-bottom:16px;flex-wrap:wrap" id="od-h-agent-stats">' +
153
+ '<div>' +
154
+ '<div class="dim" style="font-size:11px">Total agents</div>' +
155
+ '<div class="num" style="font-size:22px;font-weight:700" id="od-h-agent-total">—</div>' +
156
+ '</div>' +
157
+ '<div>' +
158
+ '<div class="dim" style="font-size:11px">Active 24h</div>' +
159
+ '<div class="num" style="font-size:22px;font-weight:700" id="od-h-agent-active">—</div>' +
160
+ '</div>' +
161
+ '<div>' +
162
+ '<div class="dim" style="font-size:11px">Total writes</div>' +
163
+ '<div class="num" style="font-size:22px;font-weight:700" id="od-h-agent-writes">—</div>' +
164
+ '</div>' +
165
+ '<div>' +
166
+ '<div class="dim" style="font-size:11px">Total recalls</div>' +
167
+ '<div class="num" style="font-size:22px;font-weight:700" id="od-h-agent-recalls">—</div>' +
168
+ '</div>' +
169
+ '</div>' +
170
+ // Agents table: 6 columns matching design
171
+ // Protocol, Trust, Writes, Recalls — no per-agent endpoint; display '—'
172
+ '<table class="tbl" id="od-h-agents-tbl">' +
173
+ '<thead><tr>' +
174
+ '<th>Agent</th>' +
175
+ '<th>Protocol</th>' +
176
+ '<th>Trust</th>' +
177
+ '<th>Writes</th>' +
178
+ '<th>Recalls</th>' +
179
+ '<th>Last seen</th>' +
180
+ '</tr></thead>' +
181
+ '<tbody id="od-h-agents-body">' +
182
+ '<tr><td colspan="6" class="dim" style="text-align:center;padding:24px">Loading…</td></tr>' +
183
+ '</tbody>' +
184
+ '</table>' +
185
+ '</div>' +
186
+ '</div>'
187
+ );
188
+ }
189
+
190
+ // ─── KPI strip ────────────────────────────────────────────────────────────
191
+
192
+ /**
193
+ * Populate the 4 KPI status cards from real API data.
194
+ * CRIT(a): no hardcoded values — all from fetch results or '—' with explicit note.
195
+ * CRIT(c): 4th card (Mesh broker) has no API endpoint → shows 'Unknown'.
196
+ *
197
+ * @param {Object|null} healthData /health response
198
+ * @param {Object|null} statsData /api/stats response
199
+ * @param {Object|null} mathData /api/v3/math/health response
200
+ */
201
+ function populateKpiStrip(healthData, statsData, mathData) {
202
+ var strip = document.getElementById('od-h-status');
203
+ if (!strip) return;
204
+
205
+ // Card 1: Daemon — from /health
206
+ var daemonCls = (healthData && healthData.status === 'ok') ? 'ok' : 'warn';
207
+ var daemonVal = daemonCls === 'ok' ? 'Healthy' : 'Degraded';
208
+ var daemonDetail = healthData
209
+ ? 'port 8765 · v' + esc(String(healthData.version || '?'))
210
+ : 'daemon unreachable';
211
+
212
+ // Card 2: Memory DB — from /api/stats
213
+ var dbMb = (statsData && statsData.overview)
214
+ ? Number(statsData.overview.db_size_mb || 0).toFixed(1)
215
+ : null;
216
+ var dbCls = dbMb !== null ? 'ok' : 'neutral';
217
+ var dbVal = dbMb !== null ? 'Healthy' : 'Unknown';
218
+ var dbDetail = dbMb !== null ? 'memory.db · ' + esc(dbMb) + ' MB' : 'stats unavailable';
219
+
220
+ // Card 3: Math layers — from /api/v3/math/health
221
+ var mathLayerCount = (mathData && mathData.health)
222
+ ? Object.keys(mathData.health).length : 0;
223
+ var mathOverall = mathData ? String(mathData.overall || '') : '';
224
+ var mathCls = statusClass(mathOverall);
225
+ var mathVal = mathCls === 'ok' ? 'Active' : (mathOverall ? esc(mathOverall) : 'Unknown');
226
+ var mathDetail = esc(String(mathLayerCount)) + ' / 3 layers online';
227
+
228
+ // Card 4: Mesh broker — TODO: no /api/mesh/* endpoint confirmed
229
+ var meshCls = 'neutral';
230
+ var meshVal = 'Unknown';
231
+ var meshDetail = 'no endpoint — configure mesh to enable'; // TODO: wire to /api/mesh/status
232
+
233
+ var CARDS = [
234
+ { label: 'Daemon', cls: daemonCls, value: daemonVal, detail: daemonDetail },
235
+ { label: 'Memory DB', cls: dbCls, value: dbVal, detail: dbDetail },
236
+ { label: 'Math layers', cls: mathCls, value: mathVal, detail: mathDetail },
237
+ { label: 'Mesh broker', cls: meshCls, value: meshVal, detail: meshDetail },
238
+ ];
239
+
240
+ strip.innerHTML = CARDS.map(function (c) {
241
+ return (
242
+ '<div class="card kpi" style="cursor:pointer">' +
243
+ '<div class="label">' + safeIcon('health') + ' ' + esc(c.label) + '</div>' +
244
+ '<div class="value" style="font-size:20px;color:' + statusColor(c.cls) + '">' + c.value + '</div>' +
245
+ '<div class="dim" style="font-size:12px;margin-top:4px">' + c.detail + '</div>' +
246
+ '</div>'
247
+ );
248
+ }).join('');
249
+ }
250
+
251
+ // ─── Math health ──────────────────────────────────────────────────────────
252
+
253
+ /**
254
+ * Render the 3 math-layer cards from /api/v3/math/health.
255
+ * Display names for known keys; falls through to raw key for unknown ones.
256
+ * CRIT(a): all values from API — description, mode, threshold, temperature.
257
+ */
258
+ function populateMathHealth(mathData) {
259
+ var el = document.getElementById('od-h-math');
260
+ if (!el) return;
261
+
262
+ if (!mathData || !mathData.health || Object.keys(mathData.health).length === 0) {
263
+ el.innerHTML = '<p class="muted" style="padding:8px 0">Math health data unavailable.</p>';
264
+ return;
265
+ }
266
+
267
+ var NAMES = {
268
+ fisher: 'Scoring layer',
269
+ sheaf: 'Consistency layer',
270
+ langevin: 'Lifecycle layer',
271
+ };
272
+
273
+ var html = '';
274
+ var layers = mathData.health;
275
+
276
+ // Map API status values to design-canonical display labels.
277
+ // 'configured' from the live API maps to 'active' (same semantic class).
278
+ var STATUS_LABEL = {
279
+ ok: 'active',
280
+ active: 'active',
281
+ configured: 'active',
282
+ running: 'active',
283
+ healthy: 'active',
284
+ warn: 'degraded',
285
+ degraded: 'degraded',
286
+ error: 'error',
287
+ critical: 'error',
288
+ };
289
+
290
+ Object.keys(layers).forEach(function (key) {
291
+ var layer = layers[key];
292
+ var bc = statusClass(layer.status);
293
+ var rawStatus = String(layer.status || '').toLowerCase();
294
+ var displayStatus = STATUS_LABEL[rawStatus] || STATUS_LABEL[bc] || esc(layer.status || 'unknown');
295
+ var displayName = NAMES[key] || esc(key);
296
+
297
+ // Key-value metadata pairs — only from real API fields
298
+ var kvs = [];
299
+ if (layer.mode != null) kvs.push(['mode', String(layer.mode)]);
300
+ if (layer.threshold != null) kvs.push(['threshold', String(layer.threshold)]);
301
+ if (layer.temperature != null) kvs.push(['temperature', String(layer.temperature)]);
302
+
303
+ var kvsHtml = kvs.map(function (kv) {
304
+ return '<span class="mono" style="font-size:12px">' +
305
+ '<span class="dim">' + esc(kv[0]) + '</span> ' + esc(kv[1]) +
306
+ '</span>';
307
+ }).join('');
308
+
309
+ html += (
310
+ '<div style="padding:14px 0;border-bottom:1px solid var(--border)">' +
311
+ '<div style="display:flex;align-items:center;gap:10px">' +
312
+ '<b>' + esc(displayName) + '</b>' +
313
+ '<span class="badge ' + bc + '">' +
314
+ '<span class="dot"></span> ' + displayStatus +
315
+ '</span>' +
316
+ '</div>' +
317
+ '<p class="muted" style="font-size:12.5px;margin-top:5px">' +
318
+ esc(layer.description || '') +
319
+ '</p>' +
320
+ (kvs.length
321
+ ? '<div style="display:flex;gap:16px;margin-top:8px;flex-wrap:wrap">' + kvsHtml + '</div>'
322
+ : '') +
323
+ '</div>'
324
+ );
325
+ });
326
+
327
+ // API's own note (clarifies config-derived vs live probe)
328
+ if (mathData.note) {
329
+ html += '<p class="dim" style="font-size:11px;margin-top:10px;font-style:italic">' +
330
+ esc(mathData.note) + '</p>';
331
+ }
332
+
333
+ el.innerHTML = html;
334
+ }
335
+
336
+ // ─── Trust signals ────────────────────────────────────────────────────────
337
+
338
+ /** Color map for known signal types — OD palette tokens. */
339
+ var SIGNAL_COLORS = {
340
+ recalled: 'var(--ok)',
341
+ high_importance: 'var(--cyan)',
342
+ high_volume: 'var(--warn)',
343
+ quick_delete: 'var(--danger)',
344
+ };
345
+
346
+ /**
347
+ * Render trust signal bars from /api/trust/stats.
348
+ * Shows percentages relative to total signals (design-faithful).
349
+ * Honest empty state when by_signal_type is empty (current live API).
350
+ */
351
+ function populateTrustSignals(trustStats) {
352
+ var modeEl = document.getElementById('od-h-trust-mode');
353
+ var avgEl = document.getElementById('od-h-trust-avg');
354
+ var totalEl = document.getElementById('od-h-trust-total');
355
+ var barsEl = document.getElementById('od-h-signals');
356
+
357
+ if (!trustStats) {
358
+ if (modeEl) modeEl.textContent = 'trust data unavailable';
359
+ if (barsEl) barsEl.innerHTML = '<p class="muted">Could not load trust data.</p>';
360
+ return;
361
+ }
362
+
363
+ if (modeEl) modeEl.textContent = 'enforcement: ' + (trustStats.enforcement || 'unknown');
364
+
365
+ if (avgEl) {
366
+ avgEl.textContent = trustStats.avg_trust_score != null
367
+ ? Number(trustStats.avg_trust_score).toFixed(2)
368
+ : '—';
369
+ }
370
+
371
+ var totalSig = Number(trustStats.total_signals || 0);
372
+ if (totalEl) totalEl.textContent = fmtNum(totalSig);
373
+
374
+ if (!barsEl) return;
375
+
376
+ var sigMap = trustStats.by_signal_type || {};
377
+ var keys = Object.keys(sigMap);
378
+
379
+ if (keys.length === 0) {
380
+ barsEl.innerHTML =
381
+ '<p class="muted" style="font-size:13px">No trust signals recorded yet. ' +
382
+ 'Signals accumulate as agents write and recall memories.</p>';
383
+ return;
384
+ }
385
+
386
+ var total = keys.reduce(function (acc, k) { return acc + Number(sigMap[k] || 0); }, 0) || 1;
387
+
388
+ barsEl.innerHTML = keys.map(function (k) {
389
+ var val = Number(sigMap[k] || 0);
390
+ var pct = Math.round((val / total) * 100);
391
+ var barColor = SIGNAL_COLORS[k] || 'var(--violet)';
392
+ return (
393
+ '<div style="margin-bottom:12px">' +
394
+ '<div style="display:flex;justify-content:space-between;font-size:12.5px;margin-bottom:5px">' +
395
+ '<span class="mono">' + esc(k) + '</span>' +
396
+ '<b class="num">' + pct + '%</b>' +
397
+ '</div>' +
398
+ '<div class="meter"><i style="width:' + pct + '%;background:' + barColor + '"></i></div>' +
399
+ '</div>'
400
+ );
401
+ }).join('');
402
+ }
403
+
404
+ // ─── Agent summary stats ──────────────────────────────────────────────────
405
+
406
+ /**
407
+ * Populate the 4-metric summary row above the agent table.
408
+ * Total agents → /api/agents stats.total_agents (real)
409
+ * Active 24h → TODO: no per-agent last_seen endpoint; displays '—'
410
+ * Total writes → /api/events/stats["memory.stored"] (real)
411
+ * Total recalls → /api/events/stats["memory.recalled"] (real)
412
+ */
413
+ function populateAgentSummary(agentsData, eventsStats) {
414
+ var totalEl = document.getElementById('od-h-agent-total');
415
+ var activeEl = document.getElementById('od-h-agent-active');
416
+ var writesEl = document.getElementById('od-h-agent-writes');
417
+ var recallsEl = document.getElementById('od-h-agent-recalls');
418
+
419
+ var stats = (agentsData && agentsData.stats) || {};
420
+ var agents = (agentsData && agentsData.agents) || [];
421
+ var total = stats.total_agents != null ? stats.total_agents : agents.length;
422
+
423
+ if (totalEl) totalEl.textContent = fmtNum(total);
424
+ // TODO: no per-agent last_seen endpoint in current API; active_24h cannot be determined
425
+ if (activeEl) activeEl.textContent = '—';
426
+
427
+ var writes = eventsStats ? (eventsStats['memory.stored'] || 0) : 0;
428
+ var recalls = eventsStats ? (eventsStats['memory.recalled'] || 0) : 0;
429
+
430
+ if (writesEl) writesEl.textContent = fmtNum(writes);
431
+ if (recallsEl) recallsEl.textContent = fmtNum(recalls);
432
+ }
433
+
434
+ // ─── Agent table ──────────────────────────────────────────────────────────
435
+
436
+ /**
437
+ * Populate the connected-agents table from /api/agents.
438
+ * CRIT(b): 6-column design layout preserved; columns with no API data show '—'.
439
+ * Real data: Agent (agent_id), Last seen (derived from registered_at)
440
+ * TODO data: Protocol, Trust, Writes, Recalls — no per-agent endpoint
441
+ * CRIT(c): agent_id goes through esc() — XSS-safe.
442
+ */
443
+ function populateAgentTable(agentsData) {
444
+ var tbody = document.getElementById('od-h-agents-body');
445
+ if (!tbody) return;
446
+
447
+ var agents = (agentsData && agentsData.agents) || [];
448
+
449
+ if (agents.length === 0) {
450
+ tbody.innerHTML =
451
+ '<tr><td colspan="6" class="dim" style="text-align:center;padding:24px">' +
452
+ 'No agents registered. Agents appear automatically when they connect via MCP, CLI, or REST.' +
453
+ '</td></tr>';
454
+ return;
455
+ }
456
+
457
+ var rows = '';
458
+ for (var i = 0; i < agents.length; i++) {
459
+ var a = agents[i];
460
+ // CRIT(c): agent_id through esc()
461
+ rows += (
462
+ '<tr>' +
463
+ '<td><b>' + esc(a.agent_id || '') + '</b></td>' +
464
+ // TODO: Protocol — no protocol field in /api/agents response
465
+ '<td><span class="badge neutral">—</span></td>' +
466
+ // TODO: Trust score — only aggregate available via /api/trust/stats, not per-agent
467
+ '<td><span class="badge neutral">—</span></td>' +
468
+ // TODO: Writes per-agent — no per-agent write count in current API
469
+ '<td class="num dim">—</td>' +
470
+ // TODO: Recalls per-agent — no per-agent recall count in current API
471
+ '<td class="num dim">—</td>' +
472
+ '<td class="dim">' + esc(timeAgo(a.registered_at)) + '</td>' +
473
+ '</tr>'
474
+ );
475
+ }
476
+ tbody.innerHTML = rows;
477
+ }
478
+
479
+ // ─── Main entry point ─────────────────────────────────────────────────────
480
+
481
+ /**
482
+ * Fetch all health endpoints, inject OD markup, and populate with real data.
483
+ * Idempotent: each call clears and re-renders the container.
484
+ *
485
+ * @param {HTMLElement} container Usually document.getElementById("health-pane").
486
+ */
487
+ function odRenderHealth(container) {
488
+ if (!container) return;
489
+
490
+ container.innerHTML =
491
+ '<div style="color:var(--fg-2);text-align:center;padding:48px 0">' +
492
+ 'Loading health data…</div>';
493
+
494
+ Promise.all([
495
+ fetch('/health').then(function (r) { return r.ok ? r.json() : null; }).catch(function () { return null; }),
496
+ fetch('/api/stats').then(function (r) { return r.ok ? r.json() : null; }).catch(function () { return null; }),
497
+ fetch('/api/v3/math/health').then(function (r) { return r.ok ? r.json() : null; }).catch(function () { return null; }),
498
+ fetch('/api/agents').then(function (r) { return r.ok ? r.json() : null; }).catch(function () { return null; }),
499
+ fetch('/api/trust/stats').then(function (r) { return r.ok ? r.json() : null; }).catch(function () { return null; }),
500
+ fetch('/api/events/stats').then(function (r) { return r.ok ? r.json() : null; }).catch(function () { return null; }),
501
+ ]).then(function (results) {
502
+ var healthData = results[0];
503
+ var statsData = results[1];
504
+ var mathData = results[2];
505
+ var agentsData = results[3];
506
+ var trustStats = results[4];
507
+ var eventsStats = results[5];
508
+
509
+ // Inject static skeleton — no API data inside
510
+ container.innerHTML = buildSkeleton();
511
+
512
+ // Populate each section with real API data
513
+ populateKpiStrip(healthData, statsData, mathData);
514
+ populateMathHealth(mathData);
515
+ populateTrustSignals(trustStats);
516
+ populateAgentSummary(agentsData, eventsStats);
517
+ populateAgentTable(agentsData);
518
+
519
+ }).catch(function (err) {
520
+ container.innerHTML =
521
+ '<div style="color:var(--danger);text-align:center;padding:48px 0">' +
522
+ 'Failed to load health data. Check the daemon is running on port 8765.</div>';
523
+ if (typeof console !== 'undefined') {
524
+ console.error('[od-health] render error:', err);
525
+ }
526
+ });
527
+ }
528
+
529
+ // ─── Public API ───────────────────────────────────────────────────────────
530
+
531
+ window.odRenderHealth = odRenderHealth;
532
+
533
+ // Auto-run: if the health pane is already in the DOM at script load, render it.
534
+ document.addEventListener('DOMContentLoaded', function () {
535
+ var pane = document.getElementById('health-pane');
536
+ if (pane) odRenderHealth(pane);
537
+ });
538
+
539
+ }());