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.
Files changed (280) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/ATTRIBUTION.md +1 -3
  3. package/CHANGELOG.md +129 -0
  4. package/README.md +205 -123
  5. package/package.json +12 -3
  6. package/plugin/.claude-plugin/plugin.json +2 -3
  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/rules/AGENTS.md +57 -18
  30. package/plugin-src/skills/slm-cache/SKILL.md +9 -1
  31. package/plugin-src/skills/slm-compress/SKILL.md +8 -1
  32. package/plugin-src/skills/slm-graph/SKILL.md +17 -3
  33. package/plugin-src/skills/slm-recall/SKILL.md +46 -10
  34. package/plugin-src/skills/slm-remember/SKILL.md +48 -1
  35. package/plugin-src/skills/slm-session/SKILL.md +24 -1
  36. package/plugin-src/skills/slm-status/SKILL.md +18 -1
  37. package/pyproject.toml +2 -1
  38. package/scripts/postinstall/validation.js +2 -0
  39. package/scripts/postinstall-interactive.js +74 -2
  40. package/src/superlocalmemory/__init__.py +1 -1
  41. package/src/superlocalmemory/access/__init__.py +3 -0
  42. package/src/superlocalmemory/access/rbac.py +477 -0
  43. package/src/superlocalmemory/cli/commands.py +228 -17
  44. package/src/superlocalmemory/cli/compress_cmd.py +17 -7
  45. package/src/superlocalmemory/cli/daemon.py +7 -0
  46. package/src/superlocalmemory/cli/loop_cmd.py +187 -0
  47. package/src/superlocalmemory/cli/main.py +49 -8
  48. package/src/superlocalmemory/cli/mesh_cmd.py +38 -0
  49. package/src/superlocalmemory/cli/optimize_cmd.py +3 -0
  50. package/src/superlocalmemory/cli/pending_store.py +49 -13
  51. package/src/superlocalmemory/cli/proxy_cmd.py +4 -0
  52. package/src/superlocalmemory/cli/scale_engine_cmd.py +6 -0
  53. package/src/superlocalmemory/cli/setup_wizard.py +22 -13
  54. package/src/superlocalmemory/cli/version_banner.py +17 -3
  55. package/src/superlocalmemory/compliance/audit.py +6 -0
  56. package/src/superlocalmemory/compliance/gdpr.py +128 -138
  57. package/src/superlocalmemory/compliance/retention.py +176 -45
  58. package/src/superlocalmemory/core/backend_orchestrator.py +23 -59
  59. package/src/superlocalmemory/core/community_summary.py +267 -0
  60. package/src/superlocalmemory/core/config.py +216 -3
  61. package/src/superlocalmemory/core/consolidation_engine.py +95 -22
  62. package/src/superlocalmemory/core/context_cache.py +61 -18
  63. package/src/superlocalmemory/core/embedding_worker.py +21 -7
  64. package/src/superlocalmemory/core/embeddings.py +131 -46
  65. package/src/superlocalmemory/core/engine.py +41 -22
  66. package/src/superlocalmemory/core/engine_ingestion.py +359 -43
  67. package/src/superlocalmemory/core/engine_wiring.py +13 -0
  68. package/src/superlocalmemory/core/entity_community.py +178 -0
  69. package/src/superlocalmemory/core/graph_analyzer.py +39 -2
  70. package/src/superlocalmemory/core/graph_pruner.py +13 -8
  71. package/src/superlocalmemory/core/ingestion_command.py +134 -25
  72. package/src/superlocalmemory/core/injection.py +12 -7
  73. package/src/superlocalmemory/core/key_expander.py +138 -0
  74. package/src/superlocalmemory/core/maintenance.py +23 -0
  75. package/src/superlocalmemory/core/maintenance_scheduler.py +17 -7
  76. package/src/superlocalmemory/core/modes.py +1 -1
  77. package/src/superlocalmemory/core/mutations.py +2 -2
  78. package/src/superlocalmemory/core/pii.py +105 -0
  79. package/src/superlocalmemory/core/progressive_abstraction.py +208 -0
  80. package/src/superlocalmemory/core/recall_pipeline.py +7 -3
  81. package/src/superlocalmemory/core/recall_worker.py +20 -6
  82. package/src/superlocalmemory/core/scale_engine.py +60 -1
  83. package/src/superlocalmemory/core/security_primitives.py +40 -2
  84. package/src/superlocalmemory/core/store_pipeline.py +186 -29
  85. package/src/superlocalmemory/core/worker_pool.py +21 -6
  86. package/src/superlocalmemory/encoding/entity_reflexion.py +200 -0
  87. package/src/superlocalmemory/encoding/entity_resolver.py +34 -24
  88. package/src/superlocalmemory/encoding/fact_extractor.py +26 -1
  89. package/src/superlocalmemory/encoding/temporal_validator.py +64 -1
  90. package/src/superlocalmemory/evolution/evolution_store.py +122 -45
  91. package/src/superlocalmemory/evolution/llm_dispatch.py +12 -1
  92. package/src/superlocalmemory/evolution/model_selection.py +160 -0
  93. package/src/superlocalmemory/evolution/mutation_generator.py +16 -0
  94. package/src/superlocalmemory/evolution/skill_evolver.py +127 -42
  95. package/src/superlocalmemory/evolution/triggers.py +22 -13
  96. package/src/superlocalmemory/graph/cozo_backend.py +43 -20
  97. package/src/superlocalmemory/hooks/adapter_base.py +5 -1
  98. package/src/superlocalmemory/hooks/auto_recall.py +13 -1
  99. package/src/superlocalmemory/hooks/claude_code_hooks.py +11 -0
  100. package/src/superlocalmemory/hooks/codex_assets.py +64 -5
  101. package/src/superlocalmemory/hooks/hook_daemon.py +20 -3
  102. package/src/superlocalmemory/hooks/hook_handlers.py +6 -1
  103. package/src/superlocalmemory/hooks/memory_protocol.py +54 -0
  104. package/src/superlocalmemory/hooks/portable_kit.py +148 -3
  105. package/src/superlocalmemory/infra/backup.py +12 -1
  106. package/src/superlocalmemory/infra/daemon_identity.py +40 -4
  107. package/src/superlocalmemory/infra/data_root.py +43 -4
  108. package/src/superlocalmemory/infra/event_bus.py +107 -24
  109. package/src/superlocalmemory/infra/rate_limiter.py +93 -0
  110. package/src/superlocalmemory/ingestion/adapter_manager.py +4 -1
  111. package/src/superlocalmemory/ingestion/credentials.py +1 -1
  112. package/src/superlocalmemory/learning/cross_project.py +28 -19
  113. package/src/superlocalmemory/learning/model_rollback.py +3 -0
  114. package/src/superlocalmemory/learning/ranker_retrain_online.py +2 -0
  115. package/src/superlocalmemory/learning/reward.py +50 -0
  116. package/src/superlocalmemory/learning/reward_proxy.py +42 -9
  117. package/src/superlocalmemory/learning/source_quality.py +523 -1
  118. package/src/superlocalmemory/loops/__init__.py +56 -0
  119. package/src/superlocalmemory/loops/budget.py +58 -0
  120. package/src/superlocalmemory/loops/engine.py +164 -0
  121. package/src/superlocalmemory/loops/ledger.py +263 -0
  122. package/src/superlocalmemory/loops/models.py +152 -0
  123. package/src/superlocalmemory/loops/rules.py +52 -0
  124. package/src/superlocalmemory/mcp/_daemon_proxy.py +3 -0
  125. package/src/superlocalmemory/mcp/_pool_adapter.py +2 -0
  126. package/src/superlocalmemory/mcp/profiles.py +103 -0
  127. package/src/superlocalmemory/mcp/server.py +32 -79
  128. package/src/superlocalmemory/mcp/tools_active.py +4 -7
  129. package/src/superlocalmemory/mcp/tools_code_graph.py +51 -5
  130. package/src/superlocalmemory/mcp/tools_core.py +12 -4
  131. package/src/superlocalmemory/mcp/tools_evolution.py +6 -3
  132. package/src/superlocalmemory/mcp/tools_learning.py +2 -2
  133. package/src/superlocalmemory/mcp/tools_loops.py +300 -0
  134. package/src/superlocalmemory/mcp/tools_mesh.py +140 -4
  135. package/src/superlocalmemory/mcp/tools_optimize.py +15 -8
  136. package/src/superlocalmemory/mesh/broker.py +237 -129
  137. package/src/superlocalmemory/mesh/remote_sync.py +50 -8
  138. package/src/superlocalmemory/optimize/NOTICE +1 -6
  139. package/src/superlocalmemory/optimize/adapters/anthropic_adapter.py +1 -4
  140. package/src/superlocalmemory/optimize/adapters/openai_adapter.py +1 -4
  141. package/src/superlocalmemory/optimize/cache/semantic.py +27 -19
  142. package/src/superlocalmemory/optimize/compress/align.py +32 -26
  143. package/src/superlocalmemory/optimize/compress/ccr.py +14 -71
  144. package/src/superlocalmemory/optimize/compress/router.py +105 -22
  145. package/src/superlocalmemory/optimize/config/defaults.py +1 -1
  146. package/src/superlocalmemory/optimize/config/schema.py +87 -4
  147. package/src/superlocalmemory/optimize/metrics/counters.py +13 -4
  148. package/src/superlocalmemory/optimize/metrics/estimator.py +0 -3
  149. package/src/superlocalmemory/optimize/proxy/_helpers.py +31 -4
  150. package/src/superlocalmemory/optimize/storage/db.py +38 -9
  151. package/src/superlocalmemory/optimize/storage/schema.py +10 -0
  152. package/src/superlocalmemory/parameterization/pattern_extractor.py +6 -3
  153. package/src/superlocalmemory/retrieval/agentic.py +1 -1
  154. package/src/superlocalmemory/retrieval/bm25_channel.py +68 -10
  155. package/src/superlocalmemory/retrieval/engine.py +221 -47
  156. package/src/superlocalmemory/retrieval/entity_channel.py +7 -5
  157. package/src/superlocalmemory/retrieval/hopfield_channel.py +9 -2
  158. package/src/superlocalmemory/retrieval/reranker.py +3 -4
  159. package/src/superlocalmemory/retrieval/semantic_channel.py +114 -21
  160. package/src/superlocalmemory/retrieval/spreading_activation.py +11 -2
  161. package/src/superlocalmemory/retrieval/temporal_channel.py +48 -9
  162. package/src/superlocalmemory/retrieval/temporal_frame.py +102 -0
  163. package/src/superlocalmemory/retrieval/temporal_validity_filter.py +135 -0
  164. package/src/superlocalmemory/retrieval/time_window.py +181 -0
  165. package/src/superlocalmemory/server/api.py +4 -4
  166. package/src/superlocalmemory/server/config_file.py +90 -0
  167. package/src/superlocalmemory/server/origin.py +50 -0
  168. package/src/superlocalmemory/server/profile_runtime.py +125 -8
  169. package/src/superlocalmemory/server/rbac_enforce.py +142 -0
  170. package/src/superlocalmemory/server/recall_health.py +24 -3
  171. package/src/superlocalmemory/server/recall_serializer.py +19 -1
  172. package/src/superlocalmemory/server/routes/abstraction.py +115 -0
  173. package/src/superlocalmemory/server/routes/agents.py +128 -38
  174. package/src/superlocalmemory/server/routes/backup.py +317 -70
  175. package/src/superlocalmemory/server/routes/behavioral.py +349 -71
  176. package/src/superlocalmemory/server/routes/brain.py +69 -12
  177. package/src/superlocalmemory/server/routes/chat.py +10 -5
  178. package/src/superlocalmemory/server/routes/compliance.py +171 -21
  179. package/src/superlocalmemory/server/routes/config_api.py +438 -0
  180. package/src/superlocalmemory/server/routes/data_io.py +30 -8
  181. package/src/superlocalmemory/server/routes/entity.py +108 -26
  182. package/src/superlocalmemory/server/routes/events.py +24 -8
  183. package/src/superlocalmemory/server/routes/evolution.py +189 -68
  184. package/src/superlocalmemory/server/routes/helpers.py +16 -1
  185. package/src/superlocalmemory/server/routes/ingest.py +7 -4
  186. package/src/superlocalmemory/server/routes/insights.py +3 -3
  187. package/src/superlocalmemory/server/routes/learning.py +289 -118
  188. package/src/superlocalmemory/server/routes/learning_telemetry.py +153 -0
  189. package/src/superlocalmemory/server/routes/lifecycle.py +59 -8
  190. package/src/superlocalmemory/server/routes/memories.py +182 -57
  191. package/src/superlocalmemory/server/routes/mesh.py +200 -31
  192. package/src/superlocalmemory/server/routes/optimize.py +33 -1
  193. package/src/superlocalmemory/server/routes/prewarm.py +2 -0
  194. package/src/superlocalmemory/server/routes/profiles.py +63 -17
  195. package/src/superlocalmemory/server/routes/ratelimit.py +132 -0
  196. package/src/superlocalmemory/server/routes/rbac.py +367 -0
  197. package/src/superlocalmemory/server/routes/stats.py +103 -158
  198. package/src/superlocalmemory/server/routes/tiers.py +11 -9
  199. package/src/superlocalmemory/server/routes/token.py +3 -13
  200. package/src/superlocalmemory/server/routes/v3_api.py +247 -89
  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 +827 -72
  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 +132 -1
  208. package/src/superlocalmemory/storage/migrations/M010_evolution_config.py +5 -0
  209. package/src/superlocalmemory/storage/migrations/M021_ingestion_log_profile.py +108 -0
  210. package/src/superlocalmemory/storage/migrations/M022_entity_aliases_profile.py +86 -0
  211. package/src/superlocalmemory/storage/migrations/M023_mesh_profile_isolation.py +194 -0
  212. package/src/superlocalmemory/storage/migrations/M024_rbac_users_roles.py +87 -0
  213. package/src/superlocalmemory/storage/migrations/M025_perf_indexes.py +90 -0
  214. package/src/superlocalmemory/storage/migrations/M026_rbac_memberships_fk.py +136 -0
  215. package/src/superlocalmemory/storage/migrations/M027_transferable_patterns_profile.py +163 -0
  216. package/src/superlocalmemory/storage/migrations/M028_fact_entity_associations.py +270 -0
  217. package/src/superlocalmemory/storage/migrations/M029_behavioral_history_indexes.py +137 -0
  218. package/src/superlocalmemory/storage/migrations/M030_entity_explorer_indexes.py +93 -0
  219. package/src/superlocalmemory/storage/migrations/__init__.py +4 -0
  220. package/src/superlocalmemory/storage/models.py +4 -0
  221. package/src/superlocalmemory/storage/schema.py +136 -1
  222. package/src/superlocalmemory/storage/schema_v32.py +2 -0
  223. package/src/superlocalmemory/storage/schema_v343.py +24 -12
  224. package/src/superlocalmemory/storage/schema_v347.py +4 -0
  225. package/src/superlocalmemory/trust/gate.py +49 -8
  226. package/src/superlocalmemory/ui/assets/slm-icon-white.svg +64 -0
  227. package/src/superlocalmemory/ui/assets/slm-icon.svg +36 -0
  228. package/src/superlocalmemory/ui/css/design-system.css +621 -0
  229. package/src/superlocalmemory/ui/css/neural-glass.css +6 -0
  230. package/src/superlocalmemory/ui/css/od-bridge.css +158 -0
  231. package/src/superlocalmemory/ui/favicon.svg +35 -4
  232. package/src/superlocalmemory/ui/index.html +303 -173
  233. package/src/superlocalmemory/ui/js/brain.js +5 -20
  234. package/src/superlocalmemory/ui/js/core.js +100 -41
  235. package/src/superlocalmemory/ui/js/dashboard.js +403 -65
  236. package/src/superlocalmemory/ui/js/event-delegation.js +102 -0
  237. package/src/superlocalmemory/ui/js/knowledge-graph.js +11 -11
  238. package/src/superlocalmemory/ui/js/math-health.js +1 -1
  239. package/src/superlocalmemory/ui/js/memories.js +15 -4
  240. package/src/superlocalmemory/ui/js/memory-chat.js +7 -7
  241. package/src/superlocalmemory/ui/js/ng-entities.js +6 -8
  242. package/src/superlocalmemory/ui/js/ng-ingestion.js +4 -4
  243. package/src/superlocalmemory/ui/js/ng-mesh.js +4 -9
  244. package/src/superlocalmemory/ui/js/ng-shell.js +8 -8
  245. package/src/superlocalmemory/ui/js/ng-skills.js +54 -2
  246. package/src/superlocalmemory/ui/js/od-agents.js +544 -0
  247. package/src/superlocalmemory/ui/js/od-auth-gate.js +257 -0
  248. package/src/superlocalmemory/ui/js/od-backup.js +871 -0
  249. package/src/superlocalmemory/ui/js/od-brain.js +816 -0
  250. package/src/superlocalmemory/ui/js/od-entities.js +579 -0
  251. package/src/superlocalmemory/ui/js/od-graph.js +600 -0
  252. package/src/superlocalmemory/ui/js/od-health.js +539 -0
  253. package/src/superlocalmemory/ui/js/od-mcp.js +508 -0
  254. package/src/superlocalmemory/ui/js/od-memories.js +929 -0
  255. package/src/superlocalmemory/ui/js/od-mesh.js +553 -0
  256. package/src/superlocalmemory/ui/js/od-operations.js +1250 -0
  257. package/src/superlocalmemory/ui/js/od-optimize.js +787 -0
  258. package/src/superlocalmemory/ui/js/od-settings.js +1107 -0
  259. package/src/superlocalmemory/ui/js/od-shell.js +809 -0
  260. package/src/superlocalmemory/ui/js/od-skills.js +600 -0
  261. package/src/superlocalmemory/ui/js/od-team.js +258 -0
  262. package/src/superlocalmemory/ui/js/profiles.js +159 -46
  263. package/src/superlocalmemory/ui/js/settings.js +17 -3
  264. package/src/superlocalmemory/ui/js/timeline.js +34 -5
  265. package/src/superlocalmemory/ui/js/trust-dashboard.js +2 -2
  266. package/src/superlocalmemory/vector/lancedb_backend.py +8 -6
  267. package/plugin-src/.mcp.json +0 -12
  268. package/plugin-src/agents/slm-memory-advisor.md +0 -44
  269. package/plugin-src/agents/slm-optimize-advisor.md +0 -38
  270. package/plugin-src/hooks/.gitkeep +0 -0
  271. package/plugin-src/hooks/hooks.json +0 -23
  272. package/plugin-src/manifest.json +0 -25
  273. package/plugin-src/requirements.txt +0 -1
  274. package/plugin-src/rules/CLAUDE.md.fragment +0 -44
  275. package/plugin-src/scripts/ensure-venv.bat +0 -122
  276. package/plugin-src/scripts/ensure-venv.sh +0 -105
  277. package/plugin-src/scripts/slm-launch +0 -23
  278. package/plugin-src/scripts/slm-launch.bat +0 -23
  279. package/plugin-src/settings.json +0 -16
  280. package/src/superlocalmemory/learning/behavioral_listener.py +0 -94
@@ -0,0 +1,579 @@
1
+ // od-entities.js — Entity Explorer
2
+ // OD design port for SuperLocalMemory dashboard.
3
+ //
4
+ // Render target : document.getElementById('entities-pane')
5
+ // Public API : window.odRenderEntities(container)
6
+ // Auto-init : fires on DOMContentLoaded if #entities-pane exists
7
+ //
8
+ // CSP-safe: no inline on* handlers; all events wired via delegation (data-od-act).
9
+ // XSS-safe: every API string passes through _esc() before DOM insertion.
10
+ // nosemgrep: innerHTML — all dynamic values escaped via _esc()
11
+ //
12
+ // Endpoints verified against live daemon at http://127.0.0.1:8765
13
+ // GET /api/entity/list?limit=N&offset=N
14
+ // → { entities[], total, limit, offset }
15
+ // entity fields: entity_id, name, type, fact_count, first_seen, last_seen,
16
+ // summary_preview, has_compiled_truth, confidence, last_compiled_at
17
+ // GET /api/entity/{name}
18
+ // → { entity_name, entity_type, compiled_truth, knowledge_summary,
19
+ // timeline[], source_fact_ids[], last_compiled_at, confidence }
20
+ // POST /api/entity/{name}/recompile (write — not called here, shown disabled)
21
+ //
22
+ // CRIT fixes applied:
23
+ // 1. XSS — _esc() wraps every interpolated string
24
+ // 2. timeline empty-state — timeline:[] in real data; shows graceful placeholder
25
+ // 3. Tab/filter state — all 5 type filters wired, no filter leaves an invisible state
26
+
27
+ (function () {
28
+ 'use strict';
29
+
30
+ // ── Utilities ──────────────────────────────────────────────────────────────
31
+
32
+ // CRIT-1: Local escapeHtml to prevent XSS if core.js loads after this module.
33
+ function _esc(s) {
34
+ if (typeof escapeHtml === 'function') return escapeHtml(s);
35
+ var d = document.createElement('div');
36
+ d.textContent = String(s == null ? '' : s);
37
+ return d.innerHTML;
38
+ }
39
+
40
+ function _fmt(iso) {
41
+ if (!iso) return '—';
42
+ var d = new Date(iso);
43
+ return isNaN(d.getTime()) ? String(iso) : d.toLocaleDateString();
44
+ }
45
+
46
+ function _ago(iso) {
47
+ if (!iso) return '';
48
+ var s = (Date.now() - new Date(iso).getTime()) / 1000;
49
+ if (s < 0) s = 0;
50
+ if (s < 60) return Math.floor(s) + 's ago';
51
+ if (s < 3600) return Math.floor(s / 60) + 'm ago';
52
+ if (s < 86400) return Math.floor(s / 3600) + 'h ago';
53
+ return Math.floor(s / 86400) + 'd ago';
54
+ }
55
+
56
+ // Map entity type → CSS variable color name (no hex — design-system tokens only)
57
+ function _typeVar(type) {
58
+ var MAP = {
59
+ person: '--violet',
60
+ concept: '--warn',
61
+ organization: '--cyan',
62
+ skill: '--ok',
63
+ event: '--ok',
64
+ place: '--cyan',
65
+ };
66
+ return 'var(' + (MAP[String(type).toLowerCase()] || '--fg-2') + ')';
67
+ }
68
+
69
+ // Map entity type → badge class
70
+ function _typeCls(type) {
71
+ var MAP = {
72
+ person: 'violet',
73
+ concept: 'warn',
74
+ organization: 'cyan',
75
+ skill: 'ok',
76
+ event: 'ok',
77
+ };
78
+ return MAP[String(type).toLowerCase()] || 'neutral';
79
+ }
80
+
81
+ // Map entity type → Bootstrap Icon class
82
+ function _typeIcon(type) {
83
+ var MAP = {
84
+ person: 'bi-person',
85
+ concept: 'bi-lightbulb',
86
+ organization: 'bi-building',
87
+ skill: 'bi-lightning-charge',
88
+ event: 'bi-calendar-event',
89
+ place: 'bi-geo-alt',
90
+ };
91
+ return MAP[String(type).toLowerCase()] || 'bi-circle';
92
+ }
93
+
94
+ // ── CSS (injected once) ────────────────────────────────────────────────────
95
+
96
+ var _cssInjected = false;
97
+ function _injectCSS() {
98
+ if (_cssInjected) return;
99
+ _cssInjected = true;
100
+ var s = document.createElement('style');
101
+ s.dataset.odModule = 'entities';
102
+ s.textContent =
103
+ '.od-tl{position:relative;padding-left:8px;}' +
104
+ '.od-tl-item{display:flex;gap:14px;padding:0 0 20px 14px;position:relative;' +
105
+ 'border-left:2px solid var(--border);margin-left:5px;}' +
106
+ '.od-tl-item:last-child{border-left-color:transparent;padding-bottom:0;}' +
107
+ '.od-tl-dot{position:absolute;left:-7px;top:2px;width:12px;height:12px;' +
108
+ 'border-radius:50%;border:2px solid var(--card);}' +
109
+ '.od-ent-row{display:flex;align-items:center;gap:12px;padding:12px 16px;' +
110
+ 'border-bottom:1px solid var(--border);cursor:pointer;transition:background .12s;}' +
111
+ '.od-ent-row:last-child{border-bottom:0;}' +
112
+ '.od-ent-row:hover{background:var(--card-2);}' +
113
+ '.od-ent-row.active{background:var(--violet-soft);}' +
114
+ '.od-ent-av{width:32px;height:32px;border-radius:9px;display:grid;place-items:center;' +
115
+ 'font-weight:700;font-size:13px;color:#fff;flex-shrink:0;}';
116
+ document.head.appendChild(s);
117
+ }
118
+
119
+ // ── Module state ───────────────────────────────────────────────────────────
120
+
121
+ var _st = {
122
+ rootId: null,
123
+ entities: [],
124
+ total: 0,
125
+ page: 0,
126
+ pageSize: 50,
127
+ typeFilter: 'all',
128
+ search: '',
129
+ selected: null, // entity name
130
+ requestSeq: 0,
131
+ };
132
+ var _searchTimer = null;
133
+
134
+ // ── Main entry ─────────────────────────────────────────────────────────────
135
+
136
+ function render(container) {
137
+ if (!container) return;
138
+ clearTimeout(_searchTimer);
139
+ _injectCSS();
140
+ var id = 'od-ent-' + Math.random().toString(36).slice(2, 8);
141
+ _st = Object.assign({}, _st, {
142
+ rootId: id, page: 0, typeFilter: 'all', search: '', selected: null, requestSeq: 0,
143
+ });
144
+ container.innerHTML = _scaffold(id);
145
+ _wire(container, id);
146
+ _loadList(id);
147
+ }
148
+
149
+ // ── Scaffold ───────────────────────────────────────────────────────────────
150
+
151
+ function _scaffold(id) {
152
+ return (
153
+ '<div id="' + id + '">' +
154
+ '<div class="page-head">' +
155
+ '<h2>Canonical entities</h2>' +
156
+ '<p style="color:var(--fg-2);margin-top:5px">' +
157
+ 'People, orgs, concepts and events resolved from your facts — ' +
158
+ 'each with a temporal timeline of how its knowledge changed over time.' +
159
+ '</p>' +
160
+ '</div>' +
161
+
162
+ '<div style="display:grid;grid-template-columns:340px 1fr;gap:20px;align-items:start">' +
163
+
164
+ // Left panel: list
165
+ '<div class="card" style="position:sticky;top:86px">' +
166
+ '<div class="card-head">' +
167
+ '<h3>Entities <span style="font-size:12px;font-weight:400;' +
168
+ 'color:var(--fg-2)" id="' + id + '-count"></span></h3>' +
169
+ '</div>' +
170
+
171
+ // Search
172
+ '<div style="padding:12px 16px;border-bottom:1px solid var(--border)">' +
173
+ '<label class="search" style="width:100%" for="' + id + '-search">' +
174
+ '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" ' +
175
+ 'stroke-width="1.8" width="15" height="15">' +
176
+ '<circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>' +
177
+ '<input id="' + id + '-search" type="text" placeholder="Search entities…" ' +
178
+ 'data-od-act="ent-search">' +
179
+ '</label>' +
180
+ '</div>' +
181
+
182
+ // Type filter chips
183
+ '<div style="display:flex;gap:6px;padding:10px 12px;flex-wrap:wrap;' +
184
+ 'border-bottom:1px solid var(--border)">' +
185
+ _typeChips(id, 'all') +
186
+ '</div>' +
187
+
188
+ // Entity list
189
+ '<div id="' + id + '-list" style="max-height:62vh;overflow-y:auto">' +
190
+ '<div style="padding:40px;text-align:center;color:var(--fg-2);font-size:13px">' +
191
+ 'Loading entities…' +
192
+ '</div>' +
193
+ '</div>' +
194
+
195
+ // Pagination
196
+ '<div id="' + id + '-pg" style="padding:10px 16px;border-top:1px solid var(--border)"></div>' +
197
+ '</div>' +
198
+
199
+ // Right panel: detail
200
+ '<div id="' + id + '-detail">' +
201
+ '<div style="padding:60px;text-align:center;color:var(--fg-2)">' +
202
+ '<i class="bi bi-person-badge" style="font-size:3rem;opacity:0.3;display:block;margin-bottom:12px"></i>' +
203
+ '<div>Select an entity to explore its knowledge and temporal timeline</div>' +
204
+ '</div>' +
205
+ '</div>' +
206
+
207
+ '</div>' +
208
+ '</div>'
209
+ );
210
+ }
211
+
212
+ // Match design exactly: All / Person / Org / Concept / Event (5 chips).
213
+ // data-type carries the API type string; label is display-only.
214
+ // TODO: Live data returns type="concept" for most entities; Person (5) exists,
215
+ // but "organization" does not appear yet — clicking Org shows empty state
216
+ // until the daemon's entity-extraction model assigns that type.
217
+ var TYPE_CHIPS = [
218
+ { val: 'all', label: 'All' },
219
+ { val: 'person', label: 'Person' },
220
+ { val: 'organization', label: 'Org' },
221
+ { val: 'concept', label: 'Concept' },
222
+ { val: 'event', label: 'Event' },
223
+ ];
224
+
225
+ function _typeChips(id, active) {
226
+ return TYPE_CHIPS.map(function (tc) {
227
+ return '<div class="chip' + (tc.val === active ? ' on' : '') + '" ' +
228
+ 'data-od-act="type-filter" data-type="' + _esc(tc.val) + '">' +
229
+ _esc(tc.label) +
230
+ '</div>';
231
+ }).join('');
232
+ }
233
+
234
+ // ── Entity list fetch & render ─────────────────────────────────────────────
235
+
236
+ function _loadList(id) {
237
+ var offset = _st.page * _st.pageSize;
238
+ var url = '/api/entity/list?limit=' + _st.pageSize + '&offset=' + offset;
239
+ if (_st.typeFilter !== 'all') url += '&type=' + encodeURIComponent(_st.typeFilter);
240
+ if (_st.search) url += '&search=' + encodeURIComponent(_st.search);
241
+ var requestSeq = _st.requestSeq + 1;
242
+ _st = Object.assign({}, _st, { requestSeq: requestSeq });
243
+ var listEl = document.getElementById(id + '-list');
244
+ if (listEl) {
245
+ listEl.innerHTML = '<div style="padding:40px;text-align:center;' +
246
+ 'color:var(--fg-2);font-size:13px">Loading entities…</div>';
247
+ }
248
+
249
+ fetch(url)
250
+ .then(function (r) {
251
+ if (!r.ok) throw new Error('HTTP ' + r.status);
252
+ return r.json();
253
+ })
254
+ .then(function (d) {
255
+ if (_st.rootId !== id || _st.requestSeq !== requestSeq) return;
256
+ _st = Object.assign({}, _st, {
257
+ entities: d.entities || [],
258
+ total: d.total || 0,
259
+ });
260
+ _renderList(id);
261
+ _renderPag(id, d.total || 0, offset);
262
+ // Auto-select first entity on initial load — matches design default state
263
+ if (!_st.selected && d.entities && d.entities.length > 0) {
264
+ _selectEntity(id, d.entities[0].name);
265
+ }
266
+ })
267
+ .catch(function (err) {
268
+ if (_st.rootId !== id || _st.requestSeq !== requestSeq) return;
269
+ var el = document.getElementById(id + '-list');
270
+ if (el) {
271
+ el.innerHTML = '<div style="padding:24px;text-align:center;' +
272
+ 'color:var(--danger);font-size:13px">Failed: ' + _esc(err.message) + '</div>';
273
+ }
274
+ });
275
+ }
276
+
277
+ function _renderList(id) {
278
+ var filtered = _st.entities;
279
+
280
+ var cntEl = document.getElementById(id + '-count');
281
+ if (cntEl) cntEl.textContent = _st.total.toLocaleString();
282
+
283
+ var listEl = document.getElementById(id + '-list');
284
+ if (!listEl) return;
285
+
286
+ if (filtered.length === 0) {
287
+ listEl.innerHTML = '<div style="padding:40px;text-align:center;' +
288
+ 'color:var(--fg-2);font-size:13px">' +
289
+ (_st.search || _st.typeFilter !== 'all' ? 'No entities match this filter.' :
290
+ 'No entities found. Entity extraction runs during consolidation.') +
291
+ '</div>';
292
+ return;
293
+ }
294
+
295
+ listEl.innerHTML = filtered.map(function (e) {
296
+ var av = (e.name || '?').charAt(0).toUpperCase();
297
+ var color = _typeVar(e.type);
298
+ var isAct = _st.selected === e.name;
299
+ return '<div class="od-ent-row' + (isAct ? ' active' : '') + '" ' +
300
+ 'data-od-act="select-entity" data-ename="' +
301
+ _esc(e.name).replace(/"/g, '&quot;') + '">' +
302
+ '<div class="od-ent-av" style="background:' + color + '">' + _esc(av) + '</div>' +
303
+ '<div style="flex:1;min-width:0">' +
304
+ '<div style="font-weight:600;white-space:nowrap;overflow:hidden;' +
305
+ 'text-overflow:ellipsis">' + _esc(e.name) + '</div>' +
306
+ '<div style="font-size:11.5px;color:var(--fg-3)">' +
307
+ _esc(e.type) + ' · ' + _esc(String(e.fact_count || 0)) + ' facts' +
308
+ '</div>' +
309
+ '</div>' +
310
+ '</div>';
311
+ }).join('');
312
+ }
313
+
314
+ function _renderPag(id, total, offset) {
315
+ var el = document.getElementById(id + '-pg');
316
+ if (!el) return;
317
+ var pg = Math.floor(offset / _st.pageSize);
318
+ var last = Math.max(0, Math.ceil(total / _st.pageSize) - 1);
319
+ if (last <= 0) { el.innerHTML = ''; return; }
320
+
321
+ el.innerHTML =
322
+ '<div style="display:flex;justify-content:center;gap:8px">' +
323
+ '<button class="btn sm" ' + (pg <= 0 ? 'disabled' :
324
+ 'data-od-act="list-pg" data-page="' + (pg - 1) + '"') + '>← Prev</button>' +
325
+ '<span style="padding:6px;font-size:12px;color:var(--fg-2)">' +
326
+ (pg + 1) + '/' + (last + 1) + '</span>' +
327
+ '<button class="btn sm" ' + (pg >= last ? 'disabled' :
328
+ 'data-od-act="list-pg" data-page="' + (pg + 1) + '"') + '>Next →</button>' +
329
+ '</div>';
330
+ }
331
+
332
+ // ── Entity detail fetch & render ───────────────────────────────────────────
333
+
334
+ function _selectEntity(id, name) {
335
+ _st = Object.assign({}, _st, { selected: name });
336
+ // Refresh active state in list
337
+ _renderList(id);
338
+
339
+ var detEl = document.getElementById(id + '-detail');
340
+ if (!detEl) return;
341
+ detEl.innerHTML = '<div style="padding:60px;text-align:center;color:var(--fg-2)">' +
342
+ '<div style="font-size:13px">Loading ' + _esc(name) + '…</div></div>';
343
+
344
+ fetch('/api/entity/' + encodeURIComponent(name))
345
+ .then(function (r) {
346
+ if (!r.ok) throw new Error('HTTP ' + r.status);
347
+ return r.json();
348
+ })
349
+ .then(function (d) { _renderDetail(id, d); })
350
+ .catch(function (err) {
351
+ if (detEl) {
352
+ detEl.innerHTML = '<div style="padding:24px;color:var(--danger);font-size:13px">' +
353
+ 'Could not load entity: ' + _esc(err.message) + '</div>';
354
+ }
355
+ });
356
+ }
357
+
358
+ function _renderDetail(id, d) {
359
+ var detEl = document.getElementById(id + '-detail');
360
+ if (!detEl) return;
361
+
362
+ // Find entity in list for metadata not in detail endpoint
363
+ var listEnt = _st.entities.find(function (e) { return e.name === d.entity_name; }) || {};
364
+ var type = d.entity_type || listEnt.type || 'concept';
365
+ var color = _typeVar(type);
366
+ var badgeCls = _typeCls(type);
367
+ var factsCount = listEnt.fact_count || (d.source_fact_ids || []).length;
368
+ var av = (d.entity_name || '?').charAt(0).toUpperCase();
369
+ // summary: prefer full knowledge_summary; truncate to ~280 chars for inline display
370
+ // (matches design's 1–2-sentence paragraph density; full text in compiled-truth card)
371
+ var summaryRaw = d.knowledge_summary || listEnt.summary_preview || '';
372
+ var summary = summaryRaw.length > 280 ? summaryRaw.substring(0, 280) + '…' : summaryRaw;
373
+
374
+ detEl.innerHTML =
375
+ // Header card: avatar · name · type+facts badges · inline summary · dates row
376
+ '<div class="card">' +
377
+ '<div class="card-pad">' +
378
+ '<div style="display:flex;align-items:center;gap:14px">' +
379
+ '<div class="od-ent-av" style="width:48px;height:48px;border-radius:11px;' +
380
+ 'font-size:18px;background:' + color + '">' + _esc(av) + '</div>' +
381
+ '<div>' +
382
+ '<h2 style="font-size:20px">' + _esc(d.entity_name || '') + '</h2>' +
383
+ '<div style="margin-top:4px">' +
384
+ '<span class="badge ' + _esc(badgeCls) + '">' + _esc(type) + '</span> ' +
385
+ '<span class="badge neutral">' + _esc(String(factsCount)) + ' facts</span>' +
386
+ '</div>' +
387
+ '</div>' +
388
+ '</div>' +
389
+ // Inline summary (matches design's <p class="muted"> placement)
390
+ (summary
391
+ ? '<p class="muted" style="margin-top:14px;line-height:1.6">' + _esc(summary) + '</p>'
392
+ : '') +
393
+ // Dates row
394
+ '<div style="display:flex;gap:26px;margin-top:16px;flex-wrap:wrap">' +
395
+ _dateField('First seen', listEnt.first_seen) +
396
+ _dateField('Last seen', listEnt.last_seen) +
397
+ _dateField('Compiled', d.last_compiled_at) +
398
+ '</div>' +
399
+ '</div>' +
400
+ '</div>' +
401
+
402
+ // Compiled truth (extra card — not in design mock but valuable live data)
403
+ (d.compiled_truth ? _summaryCard('Compiled Truth', d.compiled_truth) : '') +
404
+
405
+ // Temporal timeline
406
+ '<div class="card" style="margin-top:16px">' +
407
+ '<div class="card-head">' +
408
+ '<h3>Temporal timeline</h3>' +
409
+ '<span class="sub">how this entity\'s facts changed · 3-date model</span>' +
410
+ '</div>' +
411
+ '<div class="card-pad">' +
412
+ _renderTimeline(d) +
413
+ '</div>' +
414
+ '</div>';
415
+ }
416
+
417
+ function _dateField(label, iso) {
418
+ return '<div>' +
419
+ '<div class="dim" style="font-size:11px">' + _esc(label) + '</div>' +
420
+ '<div class="mono">' + _esc(_fmt(iso)) + '</div>' +
421
+ '</div>';
422
+ }
423
+
424
+ function _summaryCard(title, text) {
425
+ return '<div class="card" style="margin-top:16px">' +
426
+ '<div class="card-head"><h3 style="font-size:13.5px">' + _esc(title) + '</h3></div>' +
427
+ '<div class="card-pad" style="font-size:13.5px;color:var(--fg-2);line-height:1.7;' +
428
+ 'white-space:pre-wrap">' + _esc(text) + '</div>' +
429
+ '</div>';
430
+ }
431
+
432
+ // CRIT-2: timeline is [] in real data — graceful empty state, no crash
433
+ function _renderTimeline(d) {
434
+ var events = d.timeline || [];
435
+ var facts = d.source_fact_ids || [];
436
+
437
+ if (events.length === 0) {
438
+ // Build a synthetic timeline from available metadata
439
+ var listEnt = _st.entities.find(function (e) { return e.name === d.entity_name; }) || {};
440
+ var synth = [];
441
+ if (listEnt.first_seen) {
442
+ synth.push({
443
+ ts: listEnt.first_seen,
444
+ kind: 'observation',
445
+ text: 'Entity first observed in memory store.',
446
+ });
447
+ }
448
+ if (listEnt.last_seen && listEnt.last_seen !== listEnt.first_seen) {
449
+ synth.push({
450
+ ts: listEnt.last_seen,
451
+ kind: 'referenced',
452
+ text: 'Most recent memory mentioning this entity.',
453
+ });
454
+ }
455
+ if (d.last_compiled_at) {
456
+ synth.push({
457
+ ts: d.last_compiled_at,
458
+ kind: 'compiled',
459
+ text: 'Knowledge summary last compiled.',
460
+ });
461
+ }
462
+
463
+ if (synth.length === 0) {
464
+ return '<div style="color:var(--fg-3);font-size:13px;padding:8px 0">' +
465
+ 'No temporal events recorded yet. ' +
466
+ facts.length + ' source fact' + (facts.length === 1 ? '' : 's') + ' indexed.' +
467
+ // TODO: /api/entity/{name}/timeline — per-fact temporal history not yet exposed
468
+ '</div>';
469
+ }
470
+
471
+ return _buildTlHTML(synth);
472
+ }
473
+
474
+ // Real timeline data (when daemon exposes it)
475
+ var items = events.map(function (ev) {
476
+ return {
477
+ ts: ev.timestamp || ev.date || '',
478
+ kind: ev.event_type || ev.type || 'observation',
479
+ text: ev.description || ev.content || '',
480
+ };
481
+ });
482
+ return _buildTlHTML(items);
483
+ }
484
+
485
+ function _buildTlHTML(items) {
486
+ var COLORS = {
487
+ observation: 'var(--cyan)',
488
+ referenced: 'var(--violet)',
489
+ interval: 'var(--warn)',
490
+ compiled: 'var(--ok)',
491
+ };
492
+ return '<div class="od-tl">' +
493
+ items.map(function (item) {
494
+ var col = COLORS[item.kind] || 'var(--fg-3)';
495
+ return '<div class="od-tl-item">' +
496
+ '<div class="od-tl-dot" style="background:' + col + '"></div>' +
497
+ '<div>' +
498
+ '<div style="display:flex;gap:8px;align-items:center">' +
499
+ '<span class="mono dim" style="font-size:12px">' +
500
+ _esc(_fmt(item.ts)) +
501
+ '</span>' +
502
+ '<span class="badge neutral" style="font-size:10px">' +
503
+ _esc(item.kind) +
504
+ '</span>' +
505
+ '</div>' +
506
+ '<div style="margin-top:3px;font-size:13.5px">' +
507
+ _esc(item.text) +
508
+ '</div>' +
509
+ '</div>' +
510
+ '</div>';
511
+ }).join('') +
512
+ '</div>';
513
+ }
514
+
515
+ // ── Event delegation ──────────────────────────────────────────────────────
516
+
517
+ function _wire(container, id) {
518
+ container.addEventListener('click', function (e) {
519
+ var el = e.target.closest('[data-od-act]');
520
+ if (!el) return;
521
+ var act = el.dataset.odAct;
522
+
523
+ if (act === 'type-filter') {
524
+ _st = Object.assign({}, _st, { typeFilter: el.dataset.type, page: 0 });
525
+ _refreshTypeChips(id, el.dataset.type);
526
+ _loadList(id);
527
+ return;
528
+ }
529
+ if (act === 'select-entity') {
530
+ var name = el.dataset.ename;
531
+ if (name) _selectEntity(id, name);
532
+ return;
533
+ }
534
+ if (act === 'list-pg') {
535
+ var pg = parseInt(el.dataset.page, 10);
536
+ if (!isNaN(pg)) {
537
+ _st = Object.assign({}, _st, { page: pg });
538
+ _loadList(id);
539
+ }
540
+ return;
541
+ }
542
+ });
543
+
544
+ container.addEventListener('input', function (e) {
545
+ if (e.target.dataset.odAct === 'ent-search') {
546
+ _st = Object.assign({}, _st, { search: e.target.value.trim(), page: 0 });
547
+ clearTimeout(_searchTimer);
548
+ _searchTimer = setTimeout(function () {
549
+ if (_st.rootId === id) _loadList(id);
550
+ }, 250);
551
+ }
552
+ });
553
+ }
554
+
555
+ function _refreshTypeChips(id, active) {
556
+ var root = document.getElementById(id);
557
+ if (!root) return;
558
+ var wrap = root.querySelector('[data-od-act="type-filter"]');
559
+ if (!wrap) return;
560
+ var parent = wrap.parentNode;
561
+ if (!parent) return;
562
+ parent.innerHTML = _typeChips(id, active);
563
+ }
564
+
565
+ // ── Public API + auto-init ─────────────────────────────────────────────────
566
+
567
+ window.odRenderEntities = render;
568
+
569
+ if (document.readyState === 'loading') {
570
+ document.addEventListener('DOMContentLoaded', function () {
571
+ var pane = document.getElementById('entities-pane');
572
+ if (pane) render(pane);
573
+ });
574
+ } else {
575
+ var _pane = document.getElementById('entities-pane');
576
+ if (_pane) render(_pane);
577
+ }
578
+
579
+ }());