superlocalmemory 3.7.7 → 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 (262) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/ATTRIBUTION.md +1 -3
  3. package/CHANGELOG.md +85 -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 -2
  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 +96 -12
  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/auth_middleware.py +28 -0
  116. package/src/superlocalmemory/infra/backup.py +12 -1
  117. package/src/superlocalmemory/infra/daemon_identity.py +40 -4
  118. package/src/superlocalmemory/infra/data_root.py +43 -4
  119. package/src/superlocalmemory/infra/event_bus.py +107 -24
  120. package/src/superlocalmemory/infra/rate_limiter.py +93 -0
  121. package/src/superlocalmemory/ingestion/adapter_manager.py +4 -1
  122. package/src/superlocalmemory/ingestion/credentials.py +1 -1
  123. package/src/superlocalmemory/learning/cross_project.py +28 -19
  124. package/src/superlocalmemory/learning/reward_proxy.py +42 -9
  125. package/src/superlocalmemory/loops/__init__.py +56 -0
  126. package/src/superlocalmemory/loops/budget.py +58 -0
  127. package/src/superlocalmemory/loops/engine.py +164 -0
  128. package/src/superlocalmemory/loops/ledger.py +243 -0
  129. package/src/superlocalmemory/loops/models.py +152 -0
  130. package/src/superlocalmemory/loops/rules.py +52 -0
  131. package/src/superlocalmemory/mcp/_daemon_proxy.py +3 -0
  132. package/src/superlocalmemory/mcp/_pool_adapter.py +2 -0
  133. package/src/superlocalmemory/mcp/profiles.py +103 -0
  134. package/src/superlocalmemory/mcp/server.py +21 -49
  135. package/src/superlocalmemory/mcp/tools_active.py +4 -7
  136. package/src/superlocalmemory/mcp/tools_code_graph.py +51 -5
  137. package/src/superlocalmemory/mcp/tools_core.py +50 -5
  138. package/src/superlocalmemory/mcp/tools_evolution.py +6 -3
  139. package/src/superlocalmemory/mcp/tools_loops.py +300 -0
  140. package/src/superlocalmemory/mcp/tools_mesh.py +140 -4
  141. package/src/superlocalmemory/mcp/tools_optimize.py +15 -8
  142. package/src/superlocalmemory/mesh/broker.py +237 -129
  143. package/src/superlocalmemory/mesh/remote_sync.py +50 -8
  144. package/src/superlocalmemory/optimize/NOTICE +1 -6
  145. package/src/superlocalmemory/optimize/adapters/anthropic_adapter.py +1 -4
  146. package/src/superlocalmemory/optimize/adapters/openai_adapter.py +1 -4
  147. package/src/superlocalmemory/optimize/cache/semantic.py +27 -19
  148. package/src/superlocalmemory/optimize/compress/align.py +32 -26
  149. package/src/superlocalmemory/optimize/compress/ccr.py +14 -71
  150. package/src/superlocalmemory/optimize/compress/router.py +105 -22
  151. package/src/superlocalmemory/optimize/config/defaults.py +1 -1
  152. package/src/superlocalmemory/optimize/config/schema.py +87 -4
  153. package/src/superlocalmemory/optimize/metrics/counters.py +13 -4
  154. package/src/superlocalmemory/optimize/metrics/estimator.py +0 -3
  155. package/src/superlocalmemory/optimize/proxy/_helpers.py +31 -4
  156. package/src/superlocalmemory/optimize/storage/db.py +38 -9
  157. package/src/superlocalmemory/optimize/storage/schema.py +10 -0
  158. package/src/superlocalmemory/parameterization/pattern_extractor.py +6 -3
  159. package/src/superlocalmemory/retrieval/agentic.py +1 -1
  160. package/src/superlocalmemory/retrieval/bm25_channel.py +68 -10
  161. package/src/superlocalmemory/retrieval/engine.py +168 -26
  162. package/src/superlocalmemory/retrieval/entity_channel.py +7 -5
  163. package/src/superlocalmemory/retrieval/hopfield_channel.py +9 -2
  164. package/src/superlocalmemory/retrieval/semantic_channel.py +114 -21
  165. package/src/superlocalmemory/retrieval/spreading_activation.py +11 -2
  166. package/src/superlocalmemory/retrieval/temporal_channel.py +48 -9
  167. package/src/superlocalmemory/retrieval/temporal_frame.py +102 -0
  168. package/src/superlocalmemory/retrieval/temporal_validity_filter.py +135 -0
  169. package/src/superlocalmemory/retrieval/time_window.py +181 -0
  170. package/src/superlocalmemory/server/api.py +21 -4
  171. package/src/superlocalmemory/server/profile_runtime.py +125 -8
  172. package/src/superlocalmemory/server/rbac_enforce.py +142 -0
  173. package/src/superlocalmemory/server/recall_health.py +24 -3
  174. package/src/superlocalmemory/server/recall_serializer.py +19 -1
  175. package/src/superlocalmemory/server/routes/abstraction.py +115 -0
  176. package/src/superlocalmemory/server/routes/agents.py +128 -38
  177. package/src/superlocalmemory/server/routes/backup.py +34 -10
  178. package/src/superlocalmemory/server/routes/behavioral.py +13 -12
  179. package/src/superlocalmemory/server/routes/brain.py +21 -5
  180. package/src/superlocalmemory/server/routes/chat.py +72 -16
  181. package/src/superlocalmemory/server/routes/compliance.py +171 -21
  182. package/src/superlocalmemory/server/routes/config_api.py +436 -0
  183. package/src/superlocalmemory/server/routes/data_io.py +30 -8
  184. package/src/superlocalmemory/server/routes/entity.py +9 -4
  185. package/src/superlocalmemory/server/routes/events.py +24 -8
  186. package/src/superlocalmemory/server/routes/evolution.py +135 -17
  187. package/src/superlocalmemory/server/routes/helpers.py +16 -1
  188. package/src/superlocalmemory/server/routes/ingest.py +7 -4
  189. package/src/superlocalmemory/server/routes/insights.py +3 -3
  190. package/src/superlocalmemory/server/routes/learning.py +14 -14
  191. package/src/superlocalmemory/server/routes/lifecycle.py +59 -8
  192. package/src/superlocalmemory/server/routes/memories.py +221 -49
  193. package/src/superlocalmemory/server/routes/mesh.py +95 -15
  194. package/src/superlocalmemory/server/routes/optimize.py +33 -1
  195. package/src/superlocalmemory/server/routes/prewarm.py +2 -0
  196. package/src/superlocalmemory/server/routes/profiles.py +63 -17
  197. package/src/superlocalmemory/server/routes/ratelimit.py +124 -0
  198. package/src/superlocalmemory/server/routes/rbac.py +367 -0
  199. package/src/superlocalmemory/server/routes/stats.py +13 -6
  200. package/src/superlocalmemory/server/routes/tiers.py +11 -9
  201. package/src/superlocalmemory/server/routes/v3_api.py +194 -81
  202. package/src/superlocalmemory/server/routes/ws.py +5 -2
  203. package/src/superlocalmemory/server/security_middleware.py +12 -5
  204. package/src/superlocalmemory/server/ui.py +30 -5
  205. package/src/superlocalmemory/server/unified_daemon.py +431 -75
  206. package/src/superlocalmemory/server/write_identity.py +38 -8
  207. package/src/superlocalmemory/storage/database.py +265 -53
  208. package/src/superlocalmemory/storage/migration_runner.py +53 -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/models.py +4 -0
  217. package/src/superlocalmemory/storage/schema.py +87 -0
  218. package/src/superlocalmemory/storage/schema_v32.py +0 -9
  219. package/src/superlocalmemory/storage/schema_v343.py +24 -12
  220. package/src/superlocalmemory/trust/gate.py +49 -8
  221. package/src/superlocalmemory/ui/assets/slm-icon-white.svg +64 -0
  222. package/src/superlocalmemory/ui/assets/slm-icon.svg +36 -0
  223. package/src/superlocalmemory/ui/css/design-system.css +621 -0
  224. package/src/superlocalmemory/ui/css/neural-glass.css +6 -0
  225. package/src/superlocalmemory/ui/css/od-bridge.css +158 -0
  226. package/src/superlocalmemory/ui/favicon.svg +35 -4
  227. package/src/superlocalmemory/ui/index.html +306 -173
  228. package/src/superlocalmemory/ui/js/brain.js +5 -20
  229. package/src/superlocalmemory/ui/js/core.js +47 -31
  230. package/src/superlocalmemory/ui/js/dashboard.js +314 -63
  231. package/src/superlocalmemory/ui/js/event-delegation.js +102 -0
  232. package/src/superlocalmemory/ui/js/knowledge-graph.js +11 -11
  233. package/src/superlocalmemory/ui/js/math-health.js +1 -1
  234. package/src/superlocalmemory/ui/js/memories.js +15 -4
  235. package/src/superlocalmemory/ui/js/memory-chat.js +7 -7
  236. package/src/superlocalmemory/ui/js/ng-entities.js +6 -8
  237. package/src/superlocalmemory/ui/js/ng-ingestion.js +4 -4
  238. package/src/superlocalmemory/ui/js/ng-mesh.js +4 -9
  239. package/src/superlocalmemory/ui/js/ng-shell.js +8 -8
  240. package/src/superlocalmemory/ui/js/ng-skills.js +54 -2
  241. package/src/superlocalmemory/ui/js/od-agents.js +544 -0
  242. package/src/superlocalmemory/ui/js/od-auth-gate.js +257 -0
  243. package/src/superlocalmemory/ui/js/od-backup.js +780 -0
  244. package/src/superlocalmemory/ui/js/od-brain.js +779 -0
  245. package/src/superlocalmemory/ui/js/od-entities.js +579 -0
  246. package/src/superlocalmemory/ui/js/od-graph.js +593 -0
  247. package/src/superlocalmemory/ui/js/od-health.js +539 -0
  248. package/src/superlocalmemory/ui/js/od-mcp.js +508 -0
  249. package/src/superlocalmemory/ui/js/od-memories.js +887 -0
  250. package/src/superlocalmemory/ui/js/od-mesh.js +539 -0
  251. package/src/superlocalmemory/ui/js/od-operations.js +1250 -0
  252. package/src/superlocalmemory/ui/js/od-optimize.js +787 -0
  253. package/src/superlocalmemory/ui/js/od-settings.js +1053 -0
  254. package/src/superlocalmemory/ui/js/od-shell.js +593 -0
  255. package/src/superlocalmemory/ui/js/od-skills.js +573 -0
  256. package/src/superlocalmemory/ui/js/od-team.js +258 -0
  257. package/src/superlocalmemory/ui/js/profiles.js +159 -46
  258. package/src/superlocalmemory/ui/js/settings.js +2 -2
  259. package/src/superlocalmemory/ui/js/timeline.js +34 -5
  260. package/src/superlocalmemory/ui/js/trust-dashboard.js +2 -2
  261. package/src/superlocalmemory/vector/lancedb_backend.py +8 -6
  262. package/src/superlocalmemory/learning/behavioral_listener.py +0 -94
@@ -0,0 +1,102 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
3
+ // B2 (3.7.9): CSP-safe event delegation. Replaces inline on*= handlers
4
+ // (which force script-src 'unsafe-inline') with data-act-* attributes
5
+ // dispatched by document-level listeners. Registry keys are an allowlist
6
+ // by construction — no reflective window[name] invocation.
7
+ (function () {
8
+ 'use strict';
9
+
10
+ // action-key -> handler(el, event). Handlers call existing globals.
11
+ const ACTIONS = {
12
+ // --- bespoke ---
13
+ 'scroll-into': (el) => { const t = document.getElementById(el.dataset.target); if (t) t.scrollIntoView({ behavior: 'smooth', block: 'start' }); },
14
+ 'sigma-clear-search': () => { sigmaClearSearch(); const i = document.getElementById('sigma-search-input'); if (i) i.value = ''; },
15
+ 'export-all': (el, e) => { if (e) e.preventDefault(); exportAll(el.dataset.format); },
16
+ 'export-search-results': (el, e) => { if (e) e.preventDefault(); exportSearchResults(); },
17
+ 'set-memory-filter': (el) => setMemoryFilter(el.dataset.filter),
18
+ 'community-source': (el) => handleCommunitySourceToggle(el.dataset.source),
19
+ // --- value (this.value) ---
20
+ 'handle-resolution-change': (el) => handleResolutionChange(el.value),
21
+ 'sigma-filter-by-category': (el) => sigmaFilterByCategory(el.value),
22
+ 'sigma-search': (el) => sigmaSearch(el.value),
23
+ // --- no-arg ---
24
+ 'backup-learning-db': () => backupLearningDb(),
25
+ 'clear-event-stream': () => clearEventStream(),
26
+ 'compact-dry-run': () => compactDryRun(),
27
+ 'compact-execute': () => compactExecute(),
28
+ 'connect-git-hub': () => connectGitHub(),
29
+ 'connect-google-drive': () => connectGoogleDrive(),
30
+ 'copy-memory-to-clipboard': () => copyMemoryToClipboard(),
31
+ 'create-backup-now': () => createBackupNow(),
32
+ 'create-profile': () => createProfile(),
33
+ 'create-retention-policy': () => createRetentionPolicy(),
34
+ 'evaluate-tiers-now': () => evaluateTiersNow(),
35
+ 'export-backup': () => exportBackup(),
36
+ 'export-memory-as-markdown': () => exportMemoryAsMarkdown(),
37
+ 'filter-events': () => filterEvents(),
38
+ 'load-agents': () => loadAgents(),
39
+ 'load-clusters': () => loadClusters(),
40
+ 'load-compliance': () => loadCompliance(),
41
+ 'load-entity-explorer': () => loadEntityExplorer(),
42
+ 'load-graph': () => loadGraph(),
43
+ 'load-health-monitor': () => loadHealthMonitor(),
44
+ 'load-ingestion-status': () => loadIngestionStatus(),
45
+ 'load-mesh-peers': () => loadMeshPeers(),
46
+ 'load-skill-evolution': () => loadSkillEvolution(),
47
+ 'refresh-dashboard': () => refreshDashboard(),
48
+ 'reset-learning-data': () => resetLearningData(),
49
+ 'run-community-detection': () => runCommunityDetection(),
50
+ 'save-backup-config': () => saveBackupConfig(),
51
+ 'search-memories': () => searchMemories(),
52
+ 'sigma-reset-view': () => sigmaResetView(),
53
+ 'sigma-zoom-in': () => sigmaZoomIn(),
54
+ 'sigma-zoom-out': () => sigmaZoomOut(),
55
+ 'sync-cloud-now': () => syncCloudNow(),
56
+ 'toggle-dark-mode': () => toggleDarkMode(),
57
+ // --- phase 2: handlers on JS-generated markup (dynamic args via data-*) ---
58
+ 'toggle-privacy-blur': () => togglePrivacyBlur(),
59
+ 'open-settings-tab': () => { const t = document.querySelector('[data-target=settings]'); if (t) t.click(); },
60
+ 'show-chat-panel': () => showChatPanel(),
61
+ 'show-detail-panel': () => showDetailPanel(),
62
+ 'send-chat-from-input': () => sendChatFromInput(),
63
+ 'cancel-chat': () => cancelChat(),
64
+ 'send-chat-query': (el) => sendChatQuery(el.dataset.query),
65
+ 'chat-submit-on-enter': (el, e) => { if (e && e.key === 'Enter') sendChatFromInput(); },
66
+ 'citation-click': (el, e) => { if (e) e.preventDefault(); _onCitationClick(el.dataset.factId); },
67
+ 'filter-entities': (el) => filterEntities(el.value),
68
+ 'navigate-entity-page': (el) => navigateEntityPage(parseInt(el.dataset.page, 10)),
69
+ 'show-entity-detail': (el) => showEntityDetail(el.dataset.entity),
70
+ 'recompile-entity': (el) => recompileEntity(el.dataset.entity),
71
+ 'close-entity-detail': () => { const p = document.getElementById('entity-detail-panel'); if (p) p.style.display = 'none'; },
72
+ 'trigger-evolution': () => triggerEvolution(),
73
+ 'enable-evolution': () => enableEvolution(),
74
+ 'save-evolution-config': () => saveEvolutionConfig(),
75
+ 'sigma-highlight-node': (el) => sigmaHighlightNode(el.dataset.nodeId),
76
+ 'open-memory-detail': (el) => openMemoryDetail({ id: el.dataset.memId, content: el.dataset.memContent, category: el.dataset.memCategory, importance: parseFloat(el.dataset.memImportance) }, 'graph'),
77
+ 'sigma-filter-community': (el) => sigmaFilterByCommunity(parseInt(el.dataset.communityId, 10), el.dataset.communitySrc),
78
+ 'odg-select': (el) => odgSelect(el.dataset.odgId), // od-graph.js node inspector + citation clicks
79
+ 'load-memories-page': (el) => loadMemories(parseInt(el.dataset.page, 10)),
80
+ 'disconnect-destination': (el) => disconnectDestination(el.dataset.destId),
81
+ 'adapter-action': (el) => adapterAction(el.dataset.adapter, el.dataset.adapterAction),
82
+ };
83
+
84
+ const ATTR = { click: 'actClick', change: 'actChange', input: 'actInput', keydown: 'actKeydown' };
85
+
86
+ function makeHandler(type) {
87
+ const key = ATTR[type];
88
+ return function (event) {
89
+ let el = event.target;
90
+ while (el && el.nodeType === 1) {
91
+ if (el.dataset && el.dataset[key]) {
92
+ const fn = ACTIONS[el.dataset[key]];
93
+ if (typeof fn === 'function') fn(el, event);
94
+ return;
95
+ }
96
+ el = el.parentElement;
97
+ }
98
+ };
99
+ }
100
+
101
+ ['click', 'change', 'input', 'keydown'].forEach((t) => document.addEventListener(t, makeHandler(t)));
102
+ })();
@@ -457,7 +457,7 @@ function openSigmaNodeDetail(nodeId) {
457
457
  var neighbors = sigmaGraph.neighbors(nodeId);
458
458
  var neighborList = neighbors.slice(0, 10).map(function(nid) {
459
459
  var na = sigmaGraph.getNodeAttributes(nid);
460
- return '<div class="border-bottom py-1 cursor-pointer" onclick="sigmaHighlightNode(\'' + nid + '\')">'
460
+ return '<div class="border-bottom py-1 cursor-pointer" data-act-click="sigma-highlight-node" data-node-id="' + nid + '">'
461
461
  + '<small class="text-primary">' + escapeHtml((na.label || '').substring(0, 40)) + '</small>'
462
462
  + '</div>';
463
463
  }).join('');
@@ -477,7 +477,7 @@ function openSigmaNodeDetail(nodeId) {
477
477
  + '<h6 class="mb-1">Connected (' + neighbors.length + ')</h6>'
478
478
  + '<div style="max-height:250px;overflow-y:auto;">' + (neighborList || '<span class="text-muted">No connections</span>') + '</div>'
479
479
  + '<hr class="my-2">'
480
- + '<button class="btn btn-sm btn-outline-primary w-100" onclick="openMemoryDetail({id:\'' + nodeId + '\',content:\'' + escapeHtml((attrs.slm_content || '').substring(0, 80).replace(/'/g, "\\'")) + '\',category:\'' + (attrs.slm_category || '') + '\',importance:' + (attrs.slm_importance || 0.5) + '},\'graph\')"><i class="bi bi-box-arrow-up-right"></i> Full Detail</button>';
480
+ + '<button class="btn btn-sm btn-outline-primary w-100" data-act-click="open-memory-detail" data-mem-id="' + nodeId + '" data-mem-content="' + escapeHtml((attrs.slm_content || '').substring(0, 80)).replace(/"/g, '&quot;') + '" data-mem-category="' + (attrs.slm_category || '') + '" data-mem-importance="' + (attrs.slm_importance || 0.5) + '"><i class="bi bi-box-arrow-up-right"></i> Full Detail</button>';
481
481
  }
482
482
  }
483
483
 
@@ -493,7 +493,7 @@ function updateSigmaStatsPanel(data) {
493
493
  categories[cat] = (categories[cat] || 0) + 1;
494
494
  });
495
495
  var catHtml = Object.keys(categories).map(function(k) {
496
- return '<div>' + k + ': <strong>' + categories[k] + '</strong></div>';
496
+ return '<div>' + escapeHtml(k) + ': <strong>' + categories[k] + '</strong></div>';
497
497
  }).join('');
498
498
  panel.innerHTML = '<div>Nodes: <strong>' + nodes.length + '</strong></div>'
499
499
  + '<div>Edges: <strong>' + links.length + '</strong></div>'
@@ -738,14 +738,14 @@ function renderLiveCommunityPanel(communityMap) {
738
738
  // Generate simple label from most common entity
739
739
  var label = generateLiveLabel(c.community_id, c.members);
740
740
  html += '<div class="d-flex align-items-center mb-1 cursor-pointer" '
741
- + 'onclick="sigmaFilterByCommunity(' + c.community_id + ', \'' + communitySource + '\')" '
741
+ + 'data-act-click="sigma-filter-community" data-community-id="' + c.community_id + '" data-community-src="' + communitySource + '" '
742
742
  + 'title="' + c.members.length + ' memories">'
743
743
  + '<span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:' + color + ';flex-shrink:0;"></span>'
744
744
  + '<span class="ms-1 text-truncate" style="font-size:0.75rem;">' + escapeHtml(label) + '</span>'
745
745
  + '<span class="badge bg-light text-dark ms-auto" style="font-size:0.65rem;">' + c.members.length + '</span>'
746
746
  + '</div>';
747
747
  });
748
- html += '<button class="btn btn-sm btn-outline-secondary w-100 mt-1" onclick="sigmaClearSearch()">'
748
+ html += '<button class="btn btn-sm btn-outline-secondary w-100 mt-1" data-act-click="sigma-clear-search">'
749
749
  + '<i class="bi bi-x-circle"></i> Clear Filter</button>';
750
750
  panel.innerHTML = html;
751
751
  }
@@ -780,7 +780,7 @@ function loadCommunities() {
780
780
  var communities = data.communities || [];
781
781
  if (communities.length === 0) {
782
782
  panel.innerHTML = '<div class="text-muted small">No communities detected yet.</div>'
783
- + '<button class="btn btn-sm btn-outline-primary w-100 mt-1" onclick="runCommunityDetection()">'
783
+ + '<button class="btn btn-sm btn-outline-primary w-100 mt-1" data-act-click="run-community-detection">'
784
784
  + '<i class="bi bi-cpu"></i> Detect Communities</button>';
785
785
  return;
786
786
  }
@@ -789,16 +789,16 @@ function loadCommunities() {
789
789
  communities.forEach(function(c) {
790
790
  var color = c.color || SIGMA_CLUSTER_COLORS[c.community_id % SIGMA_CLUSTER_COLORS.length];
791
791
  html += '<div class="d-flex align-items-center mb-1 cursor-pointer" '
792
- + 'onclick="sigmaFilterByCommunity(' + c.community_id + ', \'backend\')" '
793
- + 'title="' + (c.top_entities || []).join(', ') + '">'
792
+ + 'data-act-click="sigma-filter-community" data-community-id="' + c.community_id + '" data-community-src="backend" '
793
+ + 'title="' + escapeHtml((c.top_entities || []).join(', ')) + '">'
794
794
  + '<span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:' + color + ';flex-shrink:0;"></span>'
795
795
  + '<span class="ms-1 text-truncate" style="font-size:0.75rem;">' + escapeHtml(c.label) + '</span>'
796
796
  + '<span class="badge bg-light text-dark ms-auto" style="font-size:0.65rem;">' + c.member_count + '</span>'
797
797
  + '</div>';
798
798
  });
799
- html += '<button class="btn btn-sm btn-outline-secondary w-100 mt-1" onclick="sigmaClearSearch()">'
799
+ html += '<button class="btn btn-sm btn-outline-secondary w-100 mt-1" data-act-click="sigma-clear-search">'
800
800
  + '<i class="bi bi-x-circle"></i> Clear Filter</button>';
801
- html += '<button class="btn btn-sm btn-outline-primary w-100 mt-1" onclick="runCommunityDetection()">'
801
+ html += '<button class="btn btn-sm btn-outline-primary w-100 mt-1" data-act-click="run-community-detection">'
802
802
  + '<i class="bi bi-arrow-clockwise"></i> Refresh</button>';
803
803
  panel.innerHTML = html;
804
804
  })
@@ -816,7 +816,7 @@ function runCommunityDetection() {
816
816
  loadCommunities();
817
817
  loadGraphSigma();
818
818
  } else {
819
- if (panel) panel.innerHTML = '<div class="text-danger small">Failed: ' + (data.error || 'Unknown') + '</div>';
819
+ if (panel) panel.innerHTML = '<div class="text-danger small">Failed: ' + escapeHtml(data.error || 'Unknown') + '</div>';
820
820
  }
821
821
  })
822
822
  .catch(function(e) {
@@ -1,5 +1,5 @@
1
1
  // SuperLocalMemory V3 — Math Health
2
- // Displays status of Fisher-Rao, sheaf cohomology, and Langevin dynamics layers.
2
+ // Displays status of the mathematical scoring, consistency, and lifecycle layers.
3
3
 
4
4
  async function loadMathHealth() {
5
5
  var CID = 'math-health-cards';
@@ -27,8 +27,13 @@ var _slmLastTotal = 0;
27
27
 
28
28
  async function loadMemories(page) {
29
29
  if (typeof page === 'number') _slmPage = Math.max(0, page);
30
- var category = document.getElementById('filter-category').value;
31
- var project = document.getElementById('filter-project').value;
30
+ // OD dashboard renders memories via odRenderMemories; the legacy
31
+ // #filter-category / #filter-project selects may be absent. Null-guard so
32
+ // this legacy loader never throws on the OD panes.
33
+ var _catEl = document.getElementById('filter-category');
34
+ var _projEl = document.getElementById('filter-project');
35
+ var category = _catEl ? _catEl.value : '';
36
+ var project = _projEl ? _projEl.value : '';
32
37
  var limit = _slmPageSize;
33
38
  var offset = _slmPage * limit;
34
39
  var url = '/api/memories?limit=' + limit + '&offset=' + offset;
@@ -70,9 +75,9 @@ function renderPaginationControls(data) {
70
75
  '<div class="d-flex justify-content-between align-items-center mt-3 small">' +
71
76
  '<span class="text-muted">Showing ' + firstIdx + '\u2013' + showing + ' of ' + total + ' memories</span>' +
72
77
  '<div class="btn-group btn-group-sm">' +
73
- '<button type="button" class="btn btn-outline-secondary" ' + prevDisabled + ' onclick="loadMemories(' + (_slmPage - 1) + ')"><i class="bi bi-chevron-left"></i> Prev</button>' +
78
+ '<button type="button" class="btn btn-outline-secondary" ' + prevDisabled + ' data-act-click="load-memories-page" data-page="' + (_slmPage - 1) + '"><i class="bi bi-chevron-left"></i> Prev</button>' +
74
79
  '<button type="button" class="btn btn-outline-secondary disabled">Page ' + (_slmPage + 1) + ' / ' + (lastPage + 1) + '</button>' +
75
- '<button type="button" class="btn btn-outline-secondary" ' + nextDisabled + ' onclick="loadMemories(' + (_slmPage + 1) + ')">Next <i class="bi bi-chevron-right"></i></button>' +
80
+ '<button type="button" class="btn btn-outline-secondary" ' + nextDisabled + ' data-act-click="load-memories-page" data-page="' + (_slmPage + 1) + '">Next <i class="bi bi-chevron-right"></i></button>' +
76
81
  '</div>' +
77
82
  '</div>';
78
83
  }
@@ -85,6 +90,12 @@ function renderMemoriesTable(memories, showScores) {
85
90
  }
86
91
 
87
92
  window._slmMemories = memories;
93
+ // OD dashboard renders memories via odRenderMemories; the legacy
94
+ // #memories-list table is absent on those panes. The cache above is set
95
+ // for OD features that read window._slmMemories, but skip the legacy DOM
96
+ // render here so this loader never throws on OD panes (mirrors the
97
+ // #filter-category / #filter-project null-guards in loadMemories).
98
+ if (!container) return;
88
99
  var scoreHeader = showScores ? '<th>Score</th>' : '';
89
100
 
90
101
  var rows = '';
@@ -34,8 +34,8 @@ function initMemoryChat() {
34
34
  + '<div class="card" style="height:550px; display:flex; flex-direction:column;">'
35
35
  + ' <div class="card-header py-2 d-flex align-items-center justify-content-between">'
36
36
  + ' <div>'
37
- + ' <button class="btn btn-sm btn-outline-primary active me-1" id="chat-tab-btn" onclick="showChatPanel()"><i class="bi bi-chat-dots"></i> Ask Memory</button>'
38
- + ' <button class="btn btn-sm btn-outline-secondary" id="detail-tab-btn" onclick="showDetailPanel()"><i class="bi bi-info-circle"></i> Detail</button>'
37
+ + ' <button class="btn btn-sm btn-outline-primary active me-1" id="chat-tab-btn" data-act-click="show-chat-panel"><i class="bi bi-chat-dots"></i> Ask Memory</button>'
38
+ + ' <button class="btn btn-sm btn-outline-secondary" id="detail-tab-btn" data-act-click="show-detail-panel"><i class="bi bi-info-circle"></i> Detail</button>'
39
39
  + ' </div>'
40
40
  + ' <span class="badge bg-secondary" id="chat-mode-badge" style="font-size:0.7rem;">Mode A</span>'
41
41
  + ' </div>'
@@ -47,7 +47,7 @@ function initMemoryChat() {
47
47
  // Quick actions
48
48
  + ' <div id="chat-quick-actions" class="px-2 py-1 border-top" style="font-size:0.75rem;">'
49
49
  + ' ' + QUICK_ACTIONS.map(function(a) {
50
- return '<button class="btn btn-sm btn-outline-secondary me-1 mb-1" onclick="sendChatQuery(\'' + a.query.replace(/'/g, "\\'") + '\')">' + a.label + '</button>';
50
+ return '<button class="btn btn-sm btn-outline-secondary me-1 mb-1" data-act-click="send-chat-query" data-query="' + escapeHtml(a.query).replace(/"/g, '&quot;') + '">' + a.label + '</button>';
51
51
  }).join('')
52
52
  + ' </div>'
53
53
 
@@ -55,11 +55,11 @@ function initMemoryChat() {
55
55
  + ' <div class="p-2 border-top">'
56
56
  + ' <div class="input-group input-group-sm">'
57
57
  + ' <input type="text" class="form-control" id="chat-input" placeholder="Ask your memory..."'
58
- + ' onkeydown="if(event.key===\'Enter\')sendChatFromInput()">'
59
- + ' <button class="btn btn-primary" onclick="sendChatFromInput()" id="chat-send-btn">'
58
+ + ' data-act-keydown="chat-submit-on-enter">'
59
+ + ' <button class="btn btn-primary" data-act-click="send-chat-from-input" id="chat-send-btn">'
60
60
  + ' <i class="bi bi-send"></i>'
61
61
  + ' </button>'
62
- + ' <button class="btn btn-outline-danger d-none" onclick="cancelChat()" id="chat-cancel-btn">'
62
+ + ' <button class="btn btn-outline-danger d-none" data-act-click="cancel-chat" id="chat-cancel-btn">'
63
63
  + ' <i class="bi bi-stop-circle"></i>'
64
64
  + ' </button>'
65
65
  + ' </div>'
@@ -241,7 +241,7 @@ function _renderMessages() {
241
241
  var citation = msg.citations[idx];
242
242
  if (citation) {
243
243
  return '<a href="#" class="badge bg-primary text-decoration-none" '
244
- + 'onclick="event.preventDefault(); _onCitationClick(\'' + citation.fact_id + '\')" '
244
+ + 'data-act-click="citation-click" data-fact-id="' + citation.fact_id + '" '
245
245
  + 'title="' + (citation.content_preview || '').replace(/"/g, '&quot;') + '">'
246
246
  + match + '</a>';
247
247
  }
@@ -49,7 +49,7 @@
49
49
  var html =
50
50
  '<div style="margin-bottom:16px">' +
51
51
  '<input type="text" class="form-control" id="entity-search-input" placeholder="Search ' + total + ' entities..." ' +
52
- 'oninput="filterEntities(this.value)" style="max-width:500px">' +
52
+ 'data-act-input="filter-entities" style="max-width:500px">' +
53
53
  '</div>';
54
54
 
55
55
  // Stats summary
@@ -76,7 +76,7 @@
76
76
  for (var i = 0; i < Math.min(pages, 10); i++) {
77
77
  var isActive = i === currentP;
78
78
  html += '<button class="ng-btn' + (isActive ? ' ng-btn-accent' : '') + '" ' +
79
- 'onclick="navigateEntityPage(' + i + ')" style="min-width:36px">' + (i + 1) + '</button>';
79
+ 'data-act-click="navigate-entity-page" data-page="' + i + '" style="min-width:36px">' + (i + 1) + '</button>';
80
80
  }
81
81
  if (pages > 10) html += '<span style="padding:8px;color:var(--ng-text-tertiary)">...</span>';
82
82
  html += '</div>';
@@ -97,10 +97,8 @@
97
97
  : '';
98
98
 
99
99
  return '<div class="col-md-6 col-lg-4">' +
100
- '<div class="ng-glass" style="padding:16px;cursor:pointer;transition:border-color 0.2s;' + borderAccent + '" ' +
101
- 'onclick="showEntityDetail(\'' + escapeAttr(entity.name) + '\')" ' +
102
- 'onmouseover="this.style.borderColor=\'var(--ng-border-prominent)\'" ' +
103
- 'onmouseout="this.style.borderColor=\'var(--ng-border-subtle)\'">' +
100
+ '<div class="ng-glass ng-hover-border" style="padding:16px;cursor:pointer;transition:border-color 0.2s;' + borderAccent + '" ' +
101
+ 'data-act-click="show-entity-detail" data-entity="' + escapeHtml(entity.name).replace(/"/g, '&quot;') + '">' +
104
102
  '<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:8px">' +
105
103
  '<div style="font-weight:590;font-size:0.9375rem;color:var(--ng-text-primary)">' +
106
104
  '<i class="bi ' + typeIcon + '" style="color:' + typeColor + ';margin-right:4px"></i>' +
@@ -143,9 +141,9 @@
143
141
  '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px">' +
144
142
  '<h5 style="margin:0"><i class="bi bi-person-badge"></i> ' + escapeHtml(entityName) + '</h5>' +
145
143
  '<div style="display:flex;gap:8px">' +
146
- '<button class="ng-btn" onclick="recompileEntity(\'' + escapeAttr(entityName) + '\')">' +
144
+ '<button class="ng-btn" data-act-click="recompile-entity" data-entity="' + escapeHtml(entityName).replace(/"/g, '&quot;') + '">' +
147
145
  '<i class="bi bi-arrow-repeat"></i> Recompile</button>' +
148
- '<button class="ng-btn" onclick="document.getElementById(\'entity-detail-panel\').style.display=\'none\'">' +
146
+ '<button class="ng-btn" data-act-click="close-entity-detail">' +
149
147
  '<i class="bi bi-x-lg"></i></button>' +
150
148
  '</div>' +
151
149
  '</div>' +
@@ -127,14 +127,14 @@
127
127
  '<div style="display:flex;gap:8px">' +
128
128
  (isEnabled ?
129
129
  (isRunning ?
130
- '<button class="ng-btn" onclick="adapterAction(\'' + adapter.name + '\',\'stop\')">' +
130
+ '<button class="ng-btn" data-act-click="adapter-action" data-adapter="' + adapter.name + '" data-adapter-action="stop">' +
131
131
  '<i class="bi bi-stop-circle"></i> Stop</button>' :
132
- '<button class="ng-btn ng-btn-accent" onclick="adapterAction(\'' + adapter.name + '\',\'start\')">' +
132
+ '<button class="ng-btn ng-btn-accent" data-act-click="adapter-action" data-adapter="' + adapter.name + '" data-adapter-action="start">' +
133
133
  '<i class="bi bi-play-circle"></i> Start</button>'
134
134
  ) +
135
- '<button class="ng-btn" onclick="adapterAction(\'' + adapter.name + '\',\'disable\')" style="color:var(--ng-status-error)">' +
135
+ '<button class="ng-btn" data-act-click="adapter-action" data-adapter="' + adapter.name + '" data-adapter-action="disable" style="color:var(--ng-status-error)">' +
136
136
  '<i class="bi bi-x-circle"></i> Disable</button>' :
137
- '<button class="ng-btn ng-btn-accent" onclick="adapterAction(\'' + adapter.name + '\',\'enable\')">' +
137
+ '<button class="ng-btn ng-btn-accent" data-act-click="adapter-action" data-adapter="' + adapter.name + '" data-adapter-action="enable">' +
138
138
  '<i class="bi bi-power"></i> Enable</button>'
139
139
  ) +
140
140
  '</div>' +
@@ -12,17 +12,12 @@
12
12
  // Dashboard callers authenticate with the install token so we never
13
13
  // embed the mesh secret in JS. Token is fetched from /internal/token
14
14
  // (loopback-only endpoint) and cached in sessionStorage for the tab session.
15
- var TOKEN_STORAGE_KEY = 'slm_install_token';
15
+ // B2 (3.7.9): keep the token in a private closure var, never sessionStorage.
16
+ var _tokenCache = null;
16
17
 
17
- function readToken() {
18
- try { return window.sessionStorage ? window.sessionStorage.getItem(TOKEN_STORAGE_KEY) : null; }
19
- catch (e) { return null; }
20
- }
18
+ function readToken() { return _tokenCache; }
21
19
 
22
- function writeToken(value) {
23
- try { if (window.sessionStorage) window.sessionStorage.setItem(TOKEN_STORAGE_KEY, value); }
24
- catch (e) { /* storage disabled */ }
25
- }
20
+ function writeToken(value) { _tokenCache = value; }
26
21
 
27
22
  function fetchTokenFromServer() {
28
23
  return fetch('/internal/token', { credentials: 'same-origin' })
@@ -115,7 +115,7 @@
115
115
 
116
116
  footer.innerHTML =
117
117
  // v3.4.10: Cloud Backup Account Widget
118
- '<div id="ng-account-widget" style="margin-bottom:10px;padding:8px;border-radius:10px;background:rgba(255,255,255,0.03);border:1px solid rgba(255,255,255,0.06);cursor:pointer;" onclick="document.querySelector(\'[data-target=settings]\')?.click()">' +
118
+ '<div id="ng-account-widget" style="margin-bottom:10px;padding:8px;border-radius:10px;background:rgba(255,255,255,0.03);border:1px solid rgba(255,255,255,0.06);cursor:pointer;" data-act-click="open-settings-tab">' +
119
119
  '<div style="display:flex;align-items:center;gap:8px;">' +
120
120
  '<span id="ng-account-avatar" style="width:28px;height:28px;border-radius:50%;background:rgba(255,255,255,0.08);display:flex;align-items:center;justify-content:center;font-size:13px;flex-shrink:0;overflow:hidden;">' +
121
121
  '<i class="bi bi-cloud-slash" style="font-size:13px;opacity:0.4;"></i>' +
@@ -128,16 +128,16 @@
128
128
  '</div>' +
129
129
  '<div id="ng-account-actions" style="display:none;margin-top:8px;padding-top:8px;border-top:1px solid rgba(255,255,255,0.05);">' +
130
130
  '<div style="display:flex;gap:4px;">' +
131
- '<button class="ng-btn" onclick="event.stopPropagation();connectGoogleDrive()" title="Connect Google Drive" style="flex:1;justify-content:center;font-size:11px;padding:4px;">' +
131
+ '<button class="ng-btn" data-act-click="connect-google-drive" title="Connect Google Drive" style="flex:1;justify-content:center;font-size:11px;padding:4px;">' +
132
132
  '<i class="bi bi-google" style="color:#4285f4;"></i>' +
133
133
  '</button>' +
134
- '<button class="ng-btn" onclick="event.stopPropagation();connectGitHub()" title="Connect GitHub" style="flex:1;justify-content:center;font-size:11px;padding:4px;">' +
134
+ '<button class="ng-btn" data-act-click="connect-git-hub" title="Connect GitHub" style="flex:1;justify-content:center;font-size:11px;padding:4px;">' +
135
135
  '<i class="bi bi-github"></i>' +
136
136
  '</button>' +
137
- '<button class="ng-btn" onclick="event.stopPropagation();syncCloudNow()" title="Sync Now" style="flex:1;justify-content:center;font-size:11px;padding:4px;">' +
137
+ '<button class="ng-btn" data-act-click="sync-cloud-now" title="Sync Now" style="flex:1;justify-content:center;font-size:11px;padding:4px;">' +
138
138
  '<i class="bi bi-cloud-upload" style="color:#00D4AA;"></i>' +
139
139
  '</button>' +
140
- '<button class="ng-btn" onclick="event.stopPropagation();exportBackup()" title="Export Backup" style="flex:1;justify-content:center;font-size:11px;padding:4px;">' +
140
+ '<button class="ng-btn" data-act-click="export-backup" title="Export Backup" style="flex:1;justify-content:center;font-size:11px;padding:4px;">' +
141
141
  '<i class="bi bi-download" style="color:#f39c12;"></i>' +
142
142
  '</button>' +
143
143
  '</div>' +
@@ -151,10 +151,10 @@
151
151
  '<button class="ng-btn" id="ng-refresh-btn" title="Refresh" style="flex:1;justify-content:center">' +
152
152
  '<i class="bi bi-arrow-clockwise"></i>' +
153
153
  '</button>' +
154
- '<button class="ng-btn" id="ng-theme-toggle" title="Toggle theme" onclick="toggleDarkMode()" style="flex:1;justify-content:center">' +
154
+ '<button class="ng-btn" id="ng-theme-toggle" title="Toggle theme" data-act-click="toggle-dark-mode" style="flex:1;justify-content:center">' +
155
155
  '<i class="bi bi-sun-fill" id="ng-theme-icon"></i>' +
156
156
  '</button>' +
157
- '<button class="ng-btn" id="ng-privacy-btn" title="Privacy blur (for screen recording)" onclick="togglePrivacyBlur()" style="flex:1;justify-content:center">' +
157
+ '<button class="ng-btn" id="ng-privacy-btn" title="Privacy blur (for screen recording)" data-act-click="toggle-privacy-blur" style="flex:1;justify-content:center">' +
158
158
  '<i class="bi bi-eye-slash" id="ng-privacy-icon"></i>' +
159
159
  '</button>' +
160
160
  '<a href="https://github.com/qualixar/superlocalmemory" target="_blank" class="ng-btn" title="Star on GitHub" style="flex:1;justify-content:center">' +
@@ -364,7 +364,7 @@
364
364
  case 'health-pane':
365
365
  // v3.4.21 / v3.4.21 (taxonomy): Health = runtime health ONLY
366
366
  // (Daemon, Events, Agents, IDEs, Math). Governance concerns live
367
- // in operations-pane. IDEs folded in v3.4.21 per Varun — they're
367
+ // in operations-pane. IDEs folded in v3.4.21 — they're
368
368
  // connected-client state, same category as Agents.
369
369
  if (typeof loadHealthMonitor === 'function') loadHealthMonitor();
370
370
  if (typeof initEventStream === 'function') initEventStream();
@@ -47,11 +47,38 @@
47
47
  '</div>' +
48
48
  '<div style="display:flex;gap:8px">' +
49
49
  (enabled
50
- ? '<button class="btn btn-sm btn-outline-success" onclick="triggerEvolution()"><i class="bi bi-play-fill"></i> Run Now</button>'
51
- : '<button class="btn btn-sm btn-outline-primary" onclick="enableEvolution()"><i class="bi bi-power"></i> Enable</button>') +
50
+ ? '<button class="btn btn-sm btn-outline-success" data-act-click="trigger-evolution"><i class="bi bi-play-fill"></i> Run Now</button>'
51
+ : '<button class="btn btn-sm btn-outline-primary" data-act-click="enable-evolution"><i class="bi bi-power"></i> Enable</button>') +
52
52
  '</div>' +
53
53
  '</div>';
54
54
 
55
+ // Per-step evolution model config (v3.7.9) — shown when enabled.
56
+ if (enabled) {
57
+ var cfg = data.config || {};
58
+ var modelOpts = function(sel) {
59
+ var cur = sel || 'auto';
60
+ if (cur === '') cur = 'auto';
61
+ return ['auto', 'haiku', 'sonnet', 'ollama'].map(function(m) {
62
+ return '<option value="' + m + '"' + (m === cur ? ' selected' : '') + '>' + m + '</option>';
63
+ }).join('');
64
+ };
65
+ html += '<div style="margin-top:12px;border-top:1px solid var(--bs-border-color);padding-top:12px">' +
66
+ '<div style="font-size:0.8125rem;font-weight:600;margin-bottom:8px">Evolution models ' +
67
+ '<span style="font-weight:400;color:var(--ng-text-tertiary,#888)">(lowest-cost by default)</span></div>' +
68
+ '<div style="display:flex;gap:12px;flex-wrap:wrap;align-items:flex-end">' +
69
+ '<label style="font-size:0.75rem">Generate<br><select id="evo-mutation-model" class="form-select form-select-sm">' + modelOpts(cfg.mutation_model) + '</select></label>' +
70
+ '<label style="font-size:0.75rem">Verify<br><select id="evo-verify-model" class="form-select form-select-sm">' + modelOpts(cfg.verify_model) + '</select></label>' +
71
+ '<label style="font-size:0.75rem">Confirm<br><select id="evo-confirm-model" class="form-select form-select-sm">' + modelOpts(cfg.confirm_model) + '</select></label>' +
72
+ '<label style="font-size:0.75rem">Max/cycle<br><input id="evo-max-cycle" type="number" min="1" max="50" value="' + (cfg.max_per_cycle || 3) + '" class="form-control form-control-sm" style="width:84px"></label>' +
73
+ '<button class="btn btn-sm btn-primary" data-act-click="save-evolution-config"><i class="bi bi-save"></i> Save</button>' +
74
+ '</div>' +
75
+ '<div style="font-size:0.7rem;color:var(--ng-text-tertiary,#888);margin-top:8px">' +
76
+ '⚠ Evolution makes background LLM calls during consolidation (capped 10/cycle, 3 cycles/day). ' +
77
+ '“auto” picks the cheapest capable model for your backend; the verifier stays independent of the generator.' +
78
+ '</div>' +
79
+ '</div>';
80
+ }
81
+
55
82
  // Evolution history (if any)
56
83
  if (data.recent && data.recent.length > 0) {
57
84
  html += '<div style="margin-top:12px;border-top:1px solid var(--bs-border-color);padding-top:12px">' +
@@ -105,6 +132,31 @@
105
132
  .catch(function(err) { alert('Error: ' + err.message); });
106
133
  };
107
134
 
135
+ window.saveEvolutionConfig = function() {
136
+ var g = function(id) { var e = document.getElementById(id); return e ? e.value : undefined; };
137
+ var body = {
138
+ mutation_model: g('evo-mutation-model'),
139
+ verify_model: g('evo-verify-model'),
140
+ confirm_model: g('evo-confirm-model'),
141
+ };
142
+ var mx = g('evo-max-cycle');
143
+ if (mx) body.max_evolutions_per_cycle = parseInt(mx, 10);
144
+ fetch('/api/evolution/config', {
145
+ method: 'POST',
146
+ headers: { 'Content-Type': 'application/json' },
147
+ body: JSON.stringify(body),
148
+ })
149
+ .then(function(r) { return r.json(); })
150
+ .then(function(data) {
151
+ if (data.ok) {
152
+ loadSkillEvolution();
153
+ } else {
154
+ alert('Could not save evolution config: ' + (data.error || 'unknown'));
155
+ }
156
+ })
157
+ .catch(function(err) { alert('Error: ' + err.message); });
158
+ };
159
+
108
160
  function fetchSkillOverview() {
109
161
  var el = document.getElementById('skills-overview-cards');
110
162
  if (!el) return;