superlocalmemory 3.4.18 → 3.4.21

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 (172) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +42 -34
  3. package/bin/slm +11 -0
  4. package/bin/slm.bat +12 -0
  5. package/package.json +4 -3
  6. package/pyproject.toml +3 -2
  7. package/scripts/build-slm-hook.ps1 +40 -0
  8. package/scripts/build-slm-hook.sh +45 -0
  9. package/scripts/build_entry.py +452 -0
  10. package/scripts/ci/stage5b_gate.sh +50 -0
  11. package/scripts/postinstall/validation.js +187 -0
  12. package/scripts/postinstall-interactive.js +756 -0
  13. package/scripts/postinstall_binary.js +287 -0
  14. package/scripts/release_manifest.py +273 -0
  15. package/scripts/slm-hook.spec +56 -0
  16. package/skills/slm-build-graph/SKILL.md +423 -0
  17. package/skills/slm-list-recent/SKILL.md +348 -0
  18. package/skills/slm-recall/SKILL.md +343 -0
  19. package/skills/slm-remember/SKILL.md +194 -0
  20. package/skills/slm-show-patterns/SKILL.md +224 -0
  21. package/skills/slm-status/SKILL.md +363 -0
  22. package/skills/slm-switch-profile/SKILL.md +442 -0
  23. package/src/superlocalmemory/cli/commands.py +219 -79
  24. package/src/superlocalmemory/cli/context_commands.py +192 -0
  25. package/src/superlocalmemory/cli/daemon.py +15 -1
  26. package/src/superlocalmemory/cli/db_migrate.py +80 -0
  27. package/src/superlocalmemory/cli/escape_hatch.py +220 -0
  28. package/src/superlocalmemory/cli/main.py +72 -1
  29. package/src/superlocalmemory/core/context_cache.py +397 -0
  30. package/src/superlocalmemory/core/embeddings.py +8 -2
  31. package/src/superlocalmemory/core/engine.py +38 -2
  32. package/src/superlocalmemory/core/engine_wiring.py +1 -1
  33. package/src/superlocalmemory/core/ram_lock.py +111 -0
  34. package/src/superlocalmemory/core/recall_pipeline.py +433 -3
  35. package/src/superlocalmemory/core/recall_worker.py +8 -3
  36. package/src/superlocalmemory/core/security_primitives.py +635 -0
  37. package/src/superlocalmemory/core/shadow_router.py +319 -0
  38. package/src/superlocalmemory/core/slm_disabled.py +87 -0
  39. package/src/superlocalmemory/core/slmignore.py +125 -0
  40. package/src/superlocalmemory/core/topic_signature.py +143 -0
  41. package/src/superlocalmemory/core/worker_pool.py +14 -3
  42. package/src/superlocalmemory/encoding/cognitive_consolidator.py +2 -2
  43. package/src/superlocalmemory/evolution/budget.py +321 -0
  44. package/src/superlocalmemory/evolution/llm_dispatch.py +508 -0
  45. package/src/superlocalmemory/evolution/skill_evolver.py +144 -94
  46. package/src/superlocalmemory/hooks/_outcome_common.py +506 -0
  47. package/src/superlocalmemory/hooks/adapter_base.py +317 -0
  48. package/src/superlocalmemory/hooks/antigravity_adapter.py +192 -0
  49. package/src/superlocalmemory/hooks/claude_code_hooks.py +33 -1
  50. package/src/superlocalmemory/hooks/context_payload.py +312 -0
  51. package/src/superlocalmemory/hooks/copilot_adapter.py +154 -0
  52. package/src/superlocalmemory/hooks/cross_platform_connector.py +90 -0
  53. package/src/superlocalmemory/hooks/cursor_adapter.py +195 -0
  54. package/src/superlocalmemory/hooks/hook_handlers.py +109 -8
  55. package/src/superlocalmemory/hooks/ide_connector.py +25 -2
  56. package/src/superlocalmemory/hooks/post_tool_async_hook.py +165 -0
  57. package/src/superlocalmemory/hooks/post_tool_outcome_hook.py +223 -0
  58. package/src/superlocalmemory/hooks/prewarm_auth.py +170 -0
  59. package/src/superlocalmemory/hooks/session_registry.py +186 -0
  60. package/src/superlocalmemory/hooks/stop_outcome_hook.py +134 -0
  61. package/src/superlocalmemory/hooks/sync_loop.py +114 -0
  62. package/src/superlocalmemory/hooks/user_prompt_hook.py +128 -0
  63. package/src/superlocalmemory/hooks/user_prompt_rehash_hook.py +202 -0
  64. package/src/superlocalmemory/infra/backup.py +3 -3
  65. package/src/superlocalmemory/infra/cloud_backup.py +2 -2
  66. package/src/superlocalmemory/infra/event_bus.py +2 -2
  67. package/src/superlocalmemory/infra/webhook_dispatcher.py +3 -3
  68. package/src/superlocalmemory/learning/arm_catalog.py +99 -0
  69. package/src/superlocalmemory/learning/bandit.py +526 -0
  70. package/src/superlocalmemory/learning/bandit_cache.py +133 -0
  71. package/src/superlocalmemory/learning/behavioral.py +53 -1
  72. package/src/superlocalmemory/learning/consolidation_cycle.py +381 -0
  73. package/src/superlocalmemory/learning/consolidation_worker.py +188 -520
  74. package/src/superlocalmemory/learning/database.py +256 -0
  75. package/src/superlocalmemory/learning/dedup_hnsw.py +413 -0
  76. package/src/superlocalmemory/learning/ensemble.py +300 -0
  77. package/src/superlocalmemory/learning/fact_outcome_joins.py +207 -0
  78. package/src/superlocalmemory/learning/forgetting_scheduler.py +55 -0
  79. package/src/superlocalmemory/learning/hnsw_dedup.py +69 -0
  80. package/src/superlocalmemory/learning/labeler.py +87 -0
  81. package/src/superlocalmemory/learning/legacy_migration.py +277 -0
  82. package/src/superlocalmemory/learning/memory_merge.py +160 -0
  83. package/src/superlocalmemory/learning/model_cache.py +269 -0
  84. package/src/superlocalmemory/learning/model_rollback.py +278 -0
  85. package/src/superlocalmemory/learning/outcome_queue.py +284 -0
  86. package/src/superlocalmemory/learning/pattern_miner.py +415 -0
  87. package/src/superlocalmemory/learning/pattern_miner_constants.py +47 -0
  88. package/src/superlocalmemory/learning/ranker.py +225 -81
  89. package/src/superlocalmemory/learning/ranker_common.py +163 -0
  90. package/src/superlocalmemory/learning/ranker_retrain_legacy.py +202 -0
  91. package/src/superlocalmemory/learning/ranker_retrain_online.py +411 -0
  92. package/src/superlocalmemory/learning/reward.py +777 -0
  93. package/src/superlocalmemory/learning/reward_archive.py +210 -0
  94. package/src/superlocalmemory/learning/reward_boost.py +201 -0
  95. package/src/superlocalmemory/learning/reward_proxy.py +326 -0
  96. package/src/superlocalmemory/learning/shadow_test.py +524 -0
  97. package/src/superlocalmemory/learning/signal_worker.py +270 -0
  98. package/src/superlocalmemory/learning/signals.py +314 -0
  99. package/src/superlocalmemory/learning/trigram_index.py +547 -0
  100. package/src/superlocalmemory/mcp/server.py +5 -5
  101. package/src/superlocalmemory/mcp/tools_context.py +183 -0
  102. package/src/superlocalmemory/mcp/tools_core.py +92 -27
  103. package/src/superlocalmemory/parameterization/soft_prompt_generator.py +13 -0
  104. package/src/superlocalmemory/retrieval/engine.py +52 -0
  105. package/src/superlocalmemory/retrieval/reranker.py +4 -2
  106. package/src/superlocalmemory/server/api.py +2 -2
  107. package/src/superlocalmemory/server/bandit_loops.py +140 -0
  108. package/src/superlocalmemory/server/middleware/__init__.py +11 -0
  109. package/src/superlocalmemory/server/middleware/security_headers.py +144 -0
  110. package/src/superlocalmemory/server/routes/backup.py +36 -13
  111. package/src/superlocalmemory/server/routes/behavioral.py +50 -19
  112. package/src/superlocalmemory/server/routes/brain.py +1234 -0
  113. package/src/superlocalmemory/server/routes/data_io.py +4 -4
  114. package/src/superlocalmemory/server/routes/events.py +2 -2
  115. package/src/superlocalmemory/server/routes/helpers.py +1 -1
  116. package/src/superlocalmemory/server/routes/learning.py +192 -7
  117. package/src/superlocalmemory/server/routes/memories.py +189 -1
  118. package/src/superlocalmemory/server/routes/prewarm.py +171 -0
  119. package/src/superlocalmemory/server/routes/profiles.py +3 -3
  120. package/src/superlocalmemory/server/routes/token.py +88 -0
  121. package/src/superlocalmemory/server/routes/ws.py +5 -5
  122. package/src/superlocalmemory/server/security_middleware.py +13 -7
  123. package/src/superlocalmemory/server/ui.py +2 -2
  124. package/src/superlocalmemory/server/unified_daemon.py +335 -3
  125. package/src/superlocalmemory/storage/migration_runner.py +545 -0
  126. package/src/superlocalmemory/storage/migrations/M001_add_signal_features_columns.py +67 -0
  127. package/src/superlocalmemory/storage/migrations/M002_model_state_history.py +132 -0
  128. package/src/superlocalmemory/storage/migrations/M003_migration_log.py +38 -0
  129. package/src/superlocalmemory/storage/migrations/M004_cross_platform_sync_log.py +46 -0
  130. package/src/superlocalmemory/storage/migrations/M005_bandit_tables.py +75 -0
  131. package/src/superlocalmemory/storage/migrations/M006_action_outcomes_reward.py +75 -0
  132. package/src/superlocalmemory/storage/migrations/M007_pending_outcomes.py +63 -0
  133. package/src/superlocalmemory/storage/migrations/M009_model_lineage.py +54 -0
  134. package/src/superlocalmemory/storage/migrations/M010_evolution_config.py +75 -0
  135. package/src/superlocalmemory/storage/migrations/M011_archive_and_merge.py +87 -0
  136. package/src/superlocalmemory/storage/migrations/M012_shadow_observations.py +72 -0
  137. package/src/superlocalmemory/storage/migrations/M013_bi_temporal_columns.py +55 -0
  138. package/src/superlocalmemory/storage/migrations/__init__.py +81 -0
  139. package/src/superlocalmemory/storage/models.py +4 -0
  140. package/src/superlocalmemory/ui/css/brain.css +409 -0
  141. package/src/superlocalmemory/ui/css/legacy-dashboard.css +645 -0
  142. package/src/superlocalmemory/ui/index.html +459 -1345
  143. package/src/superlocalmemory/ui/js/brain.js +1321 -0
  144. package/src/superlocalmemory/ui/js/clusters.js +123 -4
  145. package/src/superlocalmemory/ui/js/init.js +48 -39
  146. package/src/superlocalmemory/ui/js/memories.js +88 -2
  147. package/src/superlocalmemory/ui/js/modal.js +71 -1
  148. package/src/superlocalmemory/ui/js/ng-shell.js +101 -88
  149. package/src/superlocalmemory/ui/js/trust-dashboard.js +168 -25
  150. package/src/superlocalmemory/ui/vendor/bootstrap-icons/bootstrap-icons.css +2018 -0
  151. package/src/superlocalmemory/ui/vendor/bootstrap-icons/fonts/bootstrap-icons.woff +0 -0
  152. package/src/superlocalmemory/ui/vendor/bootstrap-icons/fonts/bootstrap-icons.woff2 +0 -0
  153. package/src/superlocalmemory/ui/vendor/bootstrap.bundle.min.js +7 -0
  154. package/src/superlocalmemory/ui/vendor/bootstrap.min.css +6 -0
  155. package/src/superlocalmemory/ui/vendor/d3.v7.min.js +2 -0
  156. package/src/superlocalmemory/ui/vendor/graphology-library.min.js +2 -0
  157. package/src/superlocalmemory/ui/vendor/graphology.umd.min.js +2 -0
  158. package/src/superlocalmemory/ui/vendor/inter-ui/inter-variable.min.css +8 -0
  159. package/src/superlocalmemory/ui/vendor/inter-ui/variable/InterVariable-Italic.woff2 +0 -0
  160. package/src/superlocalmemory/ui/vendor/inter-ui/variable/InterVariable.woff2 +0 -0
  161. package/src/superlocalmemory/ui/vendor/sigma.min.js +1 -0
  162. package/src/superlocalmemory/ui/js/behavioral.js +0 -447
  163. package/src/superlocalmemory/ui/js/graph-core.js +0 -447
  164. package/src/superlocalmemory/ui/js/graph-interactions.js +0 -351
  165. package/src/superlocalmemory/ui/js/learning.js +0 -435
  166. package/src/superlocalmemory/ui/js/patterns.js +0 -93
  167. package/src/superlocalmemory.egg-info/PKG-INFO +0 -647
  168. package/src/superlocalmemory.egg-info/SOURCES.txt +0 -335
  169. package/src/superlocalmemory.egg-info/dependency_links.txt +0 -1
  170. package/src/superlocalmemory.egg-info/entry_points.txt +0 -2
  171. package/src/superlocalmemory.egg-info/requires.txt +0 -58
  172. package/src/superlocalmemory.egg-info/top_level.txt +0 -1
@@ -1,22 +1,141 @@
1
1
  // SuperLocalMemory V3 - Clusters View
2
2
  // Part of Qualixar | https://superlocalmemory.com
3
+ //
4
+ // v3.4.21: client-side pagination + sort. /api/clusters returns all
5
+ // clusters in one shot (677 on live data) — rendering them all gave
6
+ // an unlimited scroll. State kept on ``_CLUSTERS_STATE`` so sort /
7
+ // page changes don't re-fetch.
8
+
9
+ var _CLUSTERS_STATE = {
10
+ clusters: [],
11
+ page: 0,
12
+ pageSize: 25,
13
+ sort: 'members-desc',
14
+ };
15
+
16
+ function _sortClusters(list) {
17
+ var arr = list.slice();
18
+ switch (_CLUSTERS_STATE.sort) {
19
+ case 'members-asc':
20
+ arr.sort(function (a, b) { return (a.member_count || 0) - (b.member_count || 0); });
21
+ break;
22
+ case 'recent-desc':
23
+ arr.sort(function (a, b) {
24
+ return String(b.first_memory || '').localeCompare(String(a.first_memory || ''));
25
+ });
26
+ break;
27
+ case 'members-desc':
28
+ default:
29
+ arr.sort(function (a, b) { return (b.member_count || 0) - (a.member_count || 0); });
30
+ break;
31
+ }
32
+ return arr;
33
+ }
34
+
35
+ function _renderClustersPage() {
36
+ var container = document.getElementById('clusters-list');
37
+ if (!container) return;
38
+
39
+ var all = _sortClusters(_CLUSTERS_STATE.clusters);
40
+ var total = all.length;
41
+ var pageSize = Math.max(1, _CLUSTERS_STATE.pageSize);
42
+ var maxPage = Math.max(0, Math.ceil(total / pageSize) - 1);
43
+ if (_CLUSTERS_STATE.page > maxPage) _CLUSTERS_STATE.page = maxPage;
44
+ if (_CLUSTERS_STATE.page < 0) _CLUSTERS_STATE.page = 0;
45
+ var start = _CLUSTERS_STATE.page * pageSize;
46
+ var end = Math.min(total, start + pageSize);
47
+
48
+ var info = document.getElementById('clusters-page-info');
49
+ if (info) {
50
+ info.textContent = total === 0
51
+ ? 'No rows'
52
+ : ((start + 1) + '\u2013' + end + ' of ' + total.toLocaleString());
53
+ }
54
+ var detail = document.getElementById('clusters-page-detail');
55
+ if (detail) {
56
+ detail.textContent = 'Page ' + (_CLUSTERS_STATE.page + 1) + ' of ' + Math.max(1, maxPage + 1);
57
+ }
58
+ var prev = document.getElementById('clusters-prev-btn');
59
+ var next = document.getElementById('clusters-next-btn');
60
+ if (prev) prev.disabled = _CLUSTERS_STATE.page <= 0;
61
+ if (next) next.disabled = _CLUSTERS_STATE.page >= maxPage;
62
+
63
+ if (total === 0) {
64
+ if (typeof showEmpty === 'function') {
65
+ showEmpty('clusters-list', 'collection',
66
+ 'No clusters found yet. Clusters form automatically as you store related memories.');
67
+ }
68
+ return;
69
+ }
70
+
71
+ renderClusters(all.slice(start, end));
72
+ }
73
+
74
+ function _wireClustersControls() {
75
+ var sort = document.getElementById('clusters-sort');
76
+ if (sort && !sort._wired) {
77
+ sort._wired = true;
78
+ sort.addEventListener('change', function () {
79
+ _CLUSTERS_STATE.sort = sort.value;
80
+ _CLUSTERS_STATE.page = 0;
81
+ _renderClustersPage();
82
+ });
83
+ }
84
+ var pageSize = document.getElementById('clusters-page-size');
85
+ if (pageSize && !pageSize._wired) {
86
+ pageSize._wired = true;
87
+ pageSize.addEventListener('change', function () {
88
+ _CLUSTERS_STATE.pageSize = parseInt(pageSize.value, 10) || 25;
89
+ _CLUSTERS_STATE.page = 0;
90
+ _renderClustersPage();
91
+ });
92
+ }
93
+ var prev = document.getElementById('clusters-prev-btn');
94
+ if (prev && !prev._wired) {
95
+ prev._wired = true;
96
+ prev.addEventListener('click', function () {
97
+ if (_CLUSTERS_STATE.page > 0) {
98
+ _CLUSTERS_STATE.page -= 1;
99
+ _renderClustersPage();
100
+ }
101
+ });
102
+ }
103
+ var next = document.getElementById('clusters-next-btn');
104
+ if (next && !next._wired) {
105
+ next._wired = true;
106
+ next.addEventListener('click', function () {
107
+ _CLUSTERS_STATE.page += 1;
108
+ _renderClustersPage();
109
+ });
110
+ }
111
+ }
3
112
 
4
113
  async function loadClusters() {
5
- showLoading('clusters-list', 'Loading clusters...');
114
+ _wireClustersControls();
115
+ if (typeof showLoading === 'function') {
116
+ showLoading('clusters-list', 'Loading clusters...');
117
+ }
6
118
  try {
7
119
  var response = await fetch('/api/clusters');
8
120
  var data = await response.json();
9
- renderClusters(data.clusters);
121
+ _CLUSTERS_STATE.clusters = data.clusters || [];
122
+ _CLUSTERS_STATE.page = 0;
123
+ _renderClustersPage();
10
124
  } catch (error) {
11
125
  console.error('Error loading clusters:', error);
12
- showEmpty('clusters-list', 'collection', 'Failed to load clusters');
126
+ if (typeof showEmpty === 'function') {
127
+ showEmpty('clusters-list', 'collection', 'Failed to load clusters');
128
+ }
13
129
  }
14
130
  }
15
131
 
16
132
  function renderClusters(clusters) {
17
133
  var container = document.getElementById('clusters-list');
18
134
  if (!clusters || clusters.length === 0) {
19
- showEmpty('clusters-list', 'collection', 'No clusters found yet. Clusters form automatically as you store related memories.');
135
+ if (typeof showEmpty === 'function') {
136
+ showEmpty('clusters-list', 'collection',
137
+ 'No clusters found yet. Clusters form automatically as you store related memories.');
138
+ }
20
139
  return;
21
140
  }
22
141
 
@@ -1,45 +1,54 @@
1
1
  // SuperLocalMemory V2 - Event Listener Wiring
2
2
  // Loads LAST — after all module scripts. Connects UI events to module functions.
3
-
4
- document.getElementById('memories-tab').addEventListener('shown.bs.tab', loadMemories);
5
- document.getElementById('clusters-tab').addEventListener('shown.bs.tab', loadClusters);
6
- document.getElementById('patterns-tab').addEventListener('shown.bs.tab', loadPatterns);
7
- document.getElementById('timeline-tab').addEventListener('shown.bs.tab', loadTimeline);
8
- document.getElementById('settings-tab').addEventListener('shown.bs.tab', loadSettings);
9
- document.getElementById('search-query').addEventListener('keypress', function(e) { if (e.key === 'Enter') searchMemories(); });
10
-
11
- document.getElementById('profile-select').addEventListener('change', function() {
12
- switchProfile(this.value);
13
- });
14
-
15
- document.getElementById('add-profile-btn').addEventListener('click', function() {
16
- createProfile();
17
- });
18
-
19
- var newProfileInput = document.getElementById('new-profile-name');
20
- if (newProfileInput) {
21
- newProfileInput.addEventListener('keypress', function(e) {
22
- if (e.key === 'Enter') createProfile();
3
+ //
4
+ // v3.4.21 Domain rewrite: several tabs / panes were folded together
5
+ // (Patterns/Learning/Behavioral -> Brain; Clusters/Entities -> Graph;
6
+ // Recall Lab/Timeline -> Memories; Events/Agents/Trust/Lifecycle/
7
+ // Compliance/Math/Ingestion -> Health). Every line below is now
8
+ // null-guarded so missing elements never halt JS execution — a
9
+ // silent TypeError here used to cascade into "all spinners forever"
10
+ // because ng-shell.js failed to initialise. This whole file is
11
+ // slated for deletion in the final dead-code purge; until then,
12
+ // null-guards keep it harmless.
13
+
14
+ (function wire() {
15
+ function onShown(id, fn) {
16
+ var el = document.getElementById(id);
17
+ if (el && typeof fn === 'function') {
18
+ el.addEventListener('shown.bs.tab', fn);
19
+ }
20
+ }
21
+
22
+ function on(id, ev, fn) {
23
+ var el = document.getElementById(id);
24
+ if (el) el.addEventListener(ev, fn);
25
+ }
26
+
27
+ onShown('memories-tab', typeof loadMemories === 'function' ? loadMemories : null);
28
+ onShown('clusters-tab', typeof loadClusters === 'function' ? loadClusters : null);
29
+ onShown('patterns-tab', typeof loadPatterns === 'function' ? loadPatterns : null);
30
+ onShown('timeline-tab', typeof loadTimeline === 'function' ? loadTimeline : null);
31
+ onShown('settings-tab', typeof loadSettings === 'function' ? loadSettings : null);
32
+ onShown('events-tab', typeof loadEventStats === 'function' ? loadEventStats : null);
33
+ onShown('agents-tab', typeof loadAgents === 'function' ? loadAgents : null);
34
+ onShown('learning-tab', typeof loadLearning === 'function' ? loadLearning : null);
35
+ onShown('lifecycle-tab', typeof loadLifecycle === 'function' ? loadLifecycle : null);
36
+ onShown('behavioral-tab', typeof loadBehavioral === 'function' ? loadBehavioral : null);
37
+ onShown('compliance-tab', typeof loadCompliance === 'function' ? loadCompliance : null);
38
+
39
+ on('search-query', 'keypress', function (e) {
40
+ if (e.key === 'Enter' && typeof searchMemories === 'function') searchMemories();
23
41
  });
24
- }
25
-
26
- // v2.5 tabs (graceful — elements may not exist on older installs)
27
- var eventsTab = document.getElementById('events-tab');
28
- if (eventsTab) eventsTab.addEventListener('shown.bs.tab', loadEventStats);
29
42
 
30
- var agentsTab = document.getElementById('agents-tab');
31
- if (agentsTab) agentsTab.addEventListener('shown.bs.tab', loadAgents);
32
-
33
- // v2.7 learning tab (graceful)
34
- var learningTab = document.getElementById('learning-tab');
35
- if (learningTab) learningTab.addEventListener('shown.bs.tab', loadLearning);
36
-
37
- // v2.8 tabs (graceful — elements may not exist on older installs)
38
- var lifecycleTab = document.getElementById('lifecycle-tab');
39
- if (lifecycleTab) lifecycleTab.addEventListener('shown.bs.tab', loadLifecycle);
43
+ on('profile-select', 'change', function () {
44
+ if (typeof switchProfile === 'function') switchProfile(this.value);
45
+ });
40
46
 
41
- var behavioralTab = document.getElementById('behavioral-tab');
42
- if (behavioralTab) behavioralTab.addEventListener('shown.bs.tab', loadBehavioral);
47
+ on('add-profile-btn', 'click', function () {
48
+ if (typeof createProfile === 'function') createProfile();
49
+ });
43
50
 
44
- var complianceTab = document.getElementById('compliance-tab');
45
- if (complianceTab) complianceTab.addEventListener('shown.bs.tab', loadCompliance);
51
+ on('new-profile-name', 'keypress', function (e) {
52
+ if (e.key === 'Enter' && typeof createProfile === 'function') createProfile();
53
+ });
54
+ })();
@@ -6,12 +6,29 @@
6
6
  // value passes through escapeHtml(). Data comes from our own local SQLite DB only.
7
7
  // nosemgrep: innerHTML-xss — all dynamic values escaped via escapeHtml()
8
8
 
9
+ // S9-DASH-07: active named filter ('high_reward' | 'being_forgotten' | null).
10
+ var _slmMemoryFilter = null;
11
+
12
+ function setMemoryFilter(name) {
13
+ // Toggle: second click on same pill clears.
14
+ _slmMemoryFilter = (_slmMemoryFilter === name) ? null : name;
15
+ var pills = document.querySelectorAll('.mem-filter-pill');
16
+ for (var i = 0; i < pills.length; i++) {
17
+ var active = pills[i].getAttribute('data-filter') === _slmMemoryFilter;
18
+ pills[i].classList.toggle('active', active);
19
+ }
20
+ loadMemories();
21
+ }
22
+
9
23
  async function loadMemories() {
10
24
  var category = document.getElementById('filter-category').value;
11
25
  var project = document.getElementById('filter-project').value;
12
26
  var url = '/api/memories?limit=50';
13
27
  if (category) url += '&category=' + encodeURIComponent(category);
14
28
  if (project) url += '&project_name=' + encodeURIComponent(project);
29
+ if (_slmMemoryFilter) {
30
+ url += '&filter=' + encodeURIComponent(_slmMemoryFilter);
31
+ }
15
32
 
16
33
  showLoading('memories-list', 'Loading memories...');
17
34
  try {
@@ -56,13 +73,33 @@ function renderMemoriesTable(memories, showScores) {
56
73
  }
57
74
 
58
75
  var memId = mem.memory_id || mem.id;
76
+ var factId = mem.fact_id || '';
59
77
  var expandBtnHtml = '<button class="btn btn-sm btn-outline-secondary expand-facts-btn ms-1" data-memory-id="' + escapeHtml(String(memId)) + '" title="View atomic facts">&#9660;</button>';
60
78
 
79
+ // S9-DASH-06: inline thumbs ↑/↓ on recall results. Writes a
80
+ // reward label to action_outcomes via /api/behavioral/
81
+ // report-outcome. Only shown when we have a fact_id (recall
82
+ // path) since the outcome is keyed by memory_ids.
83
+ var feedbackHtml = '';
84
+ if (showScores && factId) {
85
+ feedbackHtml = ''
86
+ + '<span class="inline-feedback" data-fact-id="'
87
+ + escapeHtml(String(factId)) + '" '
88
+ + 'style="margin-left:8px; white-space:nowrap;">'
89
+ + '<button type="button" class="btn btn-sm btn-outline-success fb-up" '
90
+ + 'title="This was helpful (+1 reward)" '
91
+ + 'style="padding:0 6px; font-size:11px;">👍</button> '
92
+ + '<button type="button" class="btn btn-sm btn-outline-danger fb-down" '
93
+ + 'title="Not helpful (0 reward)" '
94
+ + 'style="padding:0 6px; font-size:11px;">👎</button>'
95
+ + '</span>';
96
+ }
97
+
61
98
  rows += '<tr data-mem-idx="' + idx + '">'
62
99
  + '<td>' + escapeHtml(String(mem.id)) + '</td>'
63
100
  + '<td><span class="badge bg-primary">' + escapeHtml(mem.category || 'None') + '</span></td>'
64
101
  + '<td><small>' + escapeHtml(mem.project_name || '-') + '</small></td>'
65
- + '<td class="memory-content" title="' + escapeHtml(content) + '">' + escapeHtml(contentPreview) + expandBtnHtml + '</td>'
102
+ + '<td class="memory-content" title="' + escapeHtml(content) + '">' + escapeHtml(contentPreview) + expandBtnHtml + feedbackHtml + '</td>'
66
103
  + scoreCell
67
104
  + '<td><span class="badge bg-' + importanceClass + ' badge-importance">' + escapeHtml(String(importance)) + '</span></td>'
68
105
  + '<td>' + escapeHtml(String(mem.cluster_id || '-')) + '</td>'
@@ -100,8 +137,57 @@ function renderMemoriesTable(memories, showScores) {
100
137
  return;
101
138
  }
102
139
 
140
+ // S9-DASH-06: inline thumbs ↑/↓.
141
+ var fbUp = e.target.closest('.fb-up');
142
+ var fbDown = e.target.closest('.fb-down');
143
+ if (fbUp || fbDown) {
144
+ e.stopPropagation();
145
+ var btn = fbUp || fbDown;
146
+ var wrapper = btn.closest('.inline-feedback');
147
+ var fid = wrapper && wrapper.getAttribute('data-fact-id');
148
+ if (!fid) return;
149
+ var outcome = fbUp ? 'success' : 'failure';
150
+ // Disable both buttons while the request is in flight.
151
+ var allBtns = wrapper.querySelectorAll('button');
152
+ for (var i = 0; i < allBtns.length; i++) {
153
+ allBtns[i].setAttribute('disabled', 'disabled');
154
+ }
155
+ btn.textContent = '…';
156
+ fetch('/api/behavioral/report-outcome', {
157
+ method: 'POST',
158
+ headers: {'Content-Type': 'application/json'},
159
+ credentials: 'same-origin',
160
+ body: JSON.stringify({
161
+ memory_ids: [fid],
162
+ outcome: outcome,
163
+ action_type: 'inline_feedback',
164
+ context: 'search/recall result',
165
+ }),
166
+ }).then(function(r) { return r.json(); })
167
+ .then(function(json) {
168
+ if (json && json.success) {
169
+ btn.textContent = fbUp ? '✓' : '✓';
170
+ btn.style.background = fbUp ? '#2d7a2d' : '#7a2d2d';
171
+ btn.style.borderColor = fbUp ? '#2d7a2d' : '#7a2d2d';
172
+ btn.style.color = '#fff';
173
+ } else {
174
+ btn.textContent = fbUp ? '👍' : '👎';
175
+ for (var j = 0; j < allBtns.length; j++) {
176
+ allBtns[j].removeAttribute('disabled');
177
+ }
178
+ }
179
+ }).catch(function() {
180
+ btn.textContent = fbUp ? '👍' : '👎';
181
+ for (var k = 0; k < allBtns.length; k++) {
182
+ allBtns[k].removeAttribute('disabled');
183
+ }
184
+ });
185
+ return;
186
+ }
187
+
103
188
  var row = e.target.closest('tr[data-mem-idx]');
104
- if (row && !e.target.closest('.expand-facts-btn')) {
189
+ if (row && !e.target.closest('.expand-facts-btn')
190
+ && !e.target.closest('.inline-feedback')) {
105
191
  var idx = parseInt(row.getAttribute('data-mem-idx'), 10);
106
192
  if (window._slmMemories && window._slmMemories[idx]) {
107
193
  openMemoryDetail(window._slmMemories[idx]);
@@ -215,10 +215,80 @@ function openMemoryDetail(mem, source) {
215
215
  };
216
216
  actionsDiv.appendChild(editBtn);
217
217
 
218
- // Delete buttonalways available
218
+ // S9-DASH-08: Forget (soft-archive) non-destructive; row stays
219
+ // in atomic_facts with archive_status='archived' and gets a
220
+ // payload copy in memory_archive for future restore.
221
+ var forgetBtn = document.createElement('button');
222
+ forgetBtn.className = 'btn btn-outline-warning btn-sm';
223
+ forgetBtn.innerHTML = '<i class="bi bi-archive"></i> Forget';
224
+ forgetBtn.title = 'Archive this memory — hidden from recall but recoverable';
225
+ forgetBtn.onclick = function() {
226
+ if (!confirm('Forget this memory? It will be archived '
227
+ + '(hidden from recall but recoverable).')) return;
228
+ forgetBtn.disabled = true;
229
+ fetch('/api/memories/' + encodeURIComponent(mem.id) + '/forget',
230
+ {method: 'POST'})
231
+ .then(function(r) { return r.json(); })
232
+ .then(function(d) {
233
+ if (d.success) {
234
+ modal.hide();
235
+ if (typeof showToast === 'function') {
236
+ showToast('Memory archived');
237
+ }
238
+ if (typeof loadMemories === 'function') {
239
+ setTimeout(loadMemories, 300);
240
+ }
241
+ } else {
242
+ forgetBtn.disabled = false;
243
+ }
244
+ }).catch(function() { forgetBtn.disabled = false; });
245
+ };
246
+ actionsDiv.appendChild(forgetBtn);
247
+
248
+ // S9-DASH-08: Merge — this fact is a duplicate of another;
249
+ // keep the target, archive this one. Writes memory_merge_log.
250
+ var mergeBtn = document.createElement('button');
251
+ mergeBtn.className = 'btn btn-outline-info btn-sm';
252
+ mergeBtn.innerHTML = '<i class="bi bi-union"></i> Merge into...';
253
+ mergeBtn.title = 'Mark this as a duplicate of another fact';
254
+ mergeBtn.onclick = function() {
255
+ var target = prompt(
256
+ 'Merge this memory INTO which fact_id?\n\n'
257
+ + '(Paste the target fact_id — this memory will be '
258
+ + 'archived and audit-logged to memory_merge_log.)'
259
+ );
260
+ if (!target || !target.trim()) return;
261
+ mergeBtn.disabled = true;
262
+ fetch('/api/memories/' + encodeURIComponent(mem.id) + '/merge', {
263
+ method: 'POST',
264
+ headers: {'Content-Type': 'application/json'},
265
+ body: JSON.stringify({into: target.trim()}),
266
+ }).then(function(r) { return r.json(); })
267
+ .then(function(d) {
268
+ if (d.success) {
269
+ modal.hide();
270
+ if (typeof showToast === 'function') {
271
+ showToast('Merged into ' + d.into);
272
+ }
273
+ if (typeof loadMemories === 'function') {
274
+ setTimeout(loadMemories, 300);
275
+ }
276
+ } else {
277
+ mergeBtn.disabled = false;
278
+ if (typeof showToast === 'function') {
279
+ showToast('Merge failed: '
280
+ + (d && (d.error || d.detail) || 'unknown'));
281
+ }
282
+ }
283
+ }).catch(function() { mergeBtn.disabled = false; });
284
+ };
285
+ actionsDiv.appendChild(mergeBtn);
286
+
287
+ // Delete button — always available (hard delete, irreversible)
219
288
  var deleteBtn = document.createElement('button');
220
289
  deleteBtn.className = 'btn btn-outline-danger btn-sm';
221
290
  deleteBtn.innerHTML = '<i class="bi bi-trash"></i> Delete';
291
+ deleteBtn.title = 'Permanently delete (cannot be undone) — prefer Forget';
222
292
  deleteBtn.onclick = function() {
223
293
  if (!confirm('Delete this memory? This cannot be undone.')) return;
224
294
  fetch('/api/memories/' + encodeURIComponent(mem.id), {method: 'DELETE'})