superlocalmemory 3.8.0 → 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 (118) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +32 -120
  3. package/package.json +9 -2
  4. package/plugin/.claude-plugin/plugin.json +1 -2
  5. package/plugin/CLAUDE.md +3 -3
  6. package/plugin/agents/slm-governance-advisor.md +1 -1
  7. package/plugin/agents/slm-loop-runner.md +1 -1
  8. package/plugin/agents/slm-memory-advisor.md +1 -1
  9. package/plugin/agents/slm-optimize-advisor.md +1 -1
  10. package/plugin/requirements.txt +1 -1
  11. package/plugin/skills/slm-cache/SKILL.md +1 -1
  12. package/plugin/skills/slm-compress/SKILL.md +1 -1
  13. package/plugin/skills/slm-governance/SKILL.md +1 -1
  14. package/plugin/skills/slm-graph/SKILL.md +1 -1
  15. package/plugin/skills/slm-loop/SKILL.md +1 -1
  16. package/plugin/skills/slm-mesh/SKILL.md +1 -1
  17. package/plugin/skills/slm-profile/SKILL.md +1 -1
  18. package/plugin/skills/slm-recall/SKILL.md +1 -1
  19. package/plugin/skills/slm-remember/SKILL.md +1 -1
  20. package/plugin/skills/slm-scope/SKILL.md +1 -1
  21. package/plugin/skills/slm-session/SKILL.md +1 -1
  22. package/plugin/skills/slm-status/SKILL.md +1 -1
  23. package/plugin-src/rules/AGENTS.md +1 -1
  24. package/plugin-src/skills/slm-cache/SKILL.md +1 -1
  25. package/plugin-src/skills/slm-compress/SKILL.md +1 -1
  26. package/plugin-src/skills/slm-graph/SKILL.md +1 -1
  27. package/plugin-src/skills/slm-recall/SKILL.md +1 -1
  28. package/plugin-src/skills/slm-remember/SKILL.md +1 -1
  29. package/plugin-src/skills/slm-session/SKILL.md +1 -1
  30. package/plugin-src/skills/slm-status/SKILL.md +1 -1
  31. package/pyproject.toml +2 -1
  32. package/src/superlocalmemory/__init__.py +1 -1
  33. package/src/superlocalmemory/cli/commands.py +134 -7
  34. package/src/superlocalmemory/cli/daemon.py +7 -0
  35. package/src/superlocalmemory/cli/loop_cmd.py +2 -7
  36. package/src/superlocalmemory/cli/main.py +10 -4
  37. package/src/superlocalmemory/cli/version_banner.py +17 -3
  38. package/src/superlocalmemory/core/backend_orchestrator.py +18 -16
  39. package/src/superlocalmemory/core/embedding_worker.py +4 -5
  40. package/src/superlocalmemory/core/embeddings.py +119 -45
  41. package/src/superlocalmemory/core/engine.py +24 -21
  42. package/src/superlocalmemory/core/engine_ingestion.py +332 -45
  43. package/src/superlocalmemory/core/ingestion_command.py +134 -25
  44. package/src/superlocalmemory/core/injection.py +12 -7
  45. package/src/superlocalmemory/core/maintenance_scheduler.py +17 -7
  46. package/src/superlocalmemory/core/recall_pipeline.py +5 -3
  47. package/src/superlocalmemory/core/store_pipeline.py +153 -20
  48. package/src/superlocalmemory/hooks/hook_handlers.py +6 -1
  49. package/src/superlocalmemory/hooks/portable_kit.py +34 -2
  50. package/src/superlocalmemory/learning/model_rollback.py +3 -0
  51. package/src/superlocalmemory/learning/ranker_retrain_online.py +2 -0
  52. package/src/superlocalmemory/learning/reward.py +50 -0
  53. package/src/superlocalmemory/learning/source_quality.py +523 -1
  54. package/src/superlocalmemory/loops/ledger.py +25 -5
  55. package/src/superlocalmemory/mcp/server.py +11 -30
  56. package/src/superlocalmemory/mcp/tools_core.py +4 -3
  57. package/src/superlocalmemory/mcp/tools_learning.py +2 -2
  58. package/src/superlocalmemory/retrieval/engine.py +53 -21
  59. package/src/superlocalmemory/retrieval/reranker.py +3 -4
  60. package/src/superlocalmemory/server/config_file.py +90 -0
  61. package/src/superlocalmemory/server/origin.py +50 -0
  62. package/src/superlocalmemory/server/routes/backup.py +293 -70
  63. package/src/superlocalmemory/server/routes/behavioral.py +336 -59
  64. package/src/superlocalmemory/server/routes/brain.py +57 -16
  65. package/src/superlocalmemory/server/routes/config_api.py +84 -82
  66. package/src/superlocalmemory/server/routes/entity.py +100 -23
  67. package/src/superlocalmemory/server/routes/evolution.py +103 -100
  68. package/src/superlocalmemory/server/routes/learning.py +276 -105
  69. package/src/superlocalmemory/server/routes/learning_telemetry.py +153 -0
  70. package/src/superlocalmemory/server/routes/mesh.py +121 -32
  71. package/src/superlocalmemory/server/routes/ratelimit.py +33 -25
  72. package/src/superlocalmemory/server/routes/stats.py +93 -155
  73. package/src/superlocalmemory/server/routes/token.py +3 -13
  74. package/src/superlocalmemory/server/routes/v3_api.py +64 -20
  75. package/src/superlocalmemory/server/unified_daemon.py +467 -40
  76. package/src/superlocalmemory/storage/migration_runner.py +79 -1
  77. package/src/superlocalmemory/storage/migrations/M010_evolution_config.py +5 -0
  78. package/src/superlocalmemory/storage/migrations/M028_fact_entity_associations.py +270 -0
  79. package/src/superlocalmemory/storage/migrations/M029_behavioral_history_indexes.py +137 -0
  80. package/src/superlocalmemory/storage/migrations/M030_entity_explorer_indexes.py +93 -0
  81. package/src/superlocalmemory/storage/migrations/__init__.py +4 -0
  82. package/src/superlocalmemory/storage/schema.py +49 -1
  83. package/src/superlocalmemory/storage/schema_v32.py +2 -0
  84. package/src/superlocalmemory/storage/schema_v347.py +4 -0
  85. package/src/superlocalmemory/ui/index.html +3 -6
  86. package/src/superlocalmemory/ui/js/core.js +52 -9
  87. package/src/superlocalmemory/ui/js/dashboard.js +169 -82
  88. package/src/superlocalmemory/ui/js/od-backup.js +156 -65
  89. package/src/superlocalmemory/ui/js/od-brain.js +88 -51
  90. package/src/superlocalmemory/ui/js/od-entities.js +22 -22
  91. package/src/superlocalmemory/ui/js/od-graph.js +11 -4
  92. package/src/superlocalmemory/ui/js/od-memories.js +47 -5
  93. package/src/superlocalmemory/ui/js/od-mesh.js +23 -9
  94. package/src/superlocalmemory/ui/js/od-settings.js +113 -59
  95. package/src/superlocalmemory/ui/js/od-shell.js +249 -33
  96. package/src/superlocalmemory/ui/js/od-skills.js +44 -17
  97. package/src/superlocalmemory/ui/js/settings.js +15 -1
  98. package/plugin-src/.mcp.json +0 -12
  99. package/plugin-src/agents/slm-governance-advisor.md +0 -80
  100. package/plugin-src/agents/slm-loop-runner.md +0 -71
  101. package/plugin-src/agents/slm-memory-advisor.md +0 -49
  102. package/plugin-src/agents/slm-optimize-advisor.md +0 -44
  103. package/plugin-src/commands/slm-loop.md +0 -31
  104. package/plugin-src/hooks/.gitkeep +0 -0
  105. package/plugin-src/hooks/hooks.json +0 -102
  106. package/plugin-src/manifest.json +0 -30
  107. package/plugin-src/requirements.txt +0 -1
  108. package/plugin-src/rules/CLAUDE.md.fragment +0 -44
  109. package/plugin-src/scripts/ensure-venv.bat +0 -122
  110. package/plugin-src/scripts/ensure-venv.sh +0 -105
  111. package/plugin-src/scripts/slm-launch +0 -62
  112. package/plugin-src/scripts/slm-launch.bat +0 -23
  113. package/plugin-src/settings.json +0 -25
  114. package/plugin-src/skills/slm-governance/SKILL.md +0 -248
  115. package/plugin-src/skills/slm-loop/SKILL.md +0 -99
  116. package/plugin-src/skills/slm-mesh/SKILL.md +0 -282
  117. package/plugin-src/skills/slm-profile/SKILL.md +0 -148
  118. package/plugin-src/skills/slm-scope/SKILL.md +0 -176
@@ -7,8 +7,8 @@
7
7
  // Design-system CSS classes used throughout — no invented inline colours.
8
8
  //
9
9
  // Endpoints (all via meshFetch → X-Install-Token):
10
- // GET /mesh/status → {broker_up, peer_count, uptime_s}
11
- // GET /mesh/peers → {peers:[{peer_id, session_id, status, last_heartbeat,
10
+ // GET /mesh/status → {broker_up, remote_peer_count, local_session_count, uptime_s}
11
+ // GET /mesh/peers?view=all → {peers:[{peer_id, session_id, status, last_heartbeat,
12
12
  // summary, host, port, agent_type, project_path}]}
13
13
  // POST /mesh/send → body:{from_peer, to_peer, content, type} → {ok:true}
14
14
  //
@@ -76,6 +76,7 @@
76
76
  var _container = null;
77
77
  var _timer = null;
78
78
  var _observer = null; // MutationObserver — re-starts refresh on tab activate
79
+ var _requestSeq = 0;
79
80
 
80
81
  // ── HTML helpers ──────────────────────────────────────────────────────────
81
82
  // CRIT C3: every API-derived string MUST pass through this.
@@ -198,12 +199,15 @@
198
199
  return;
199
200
  }
200
201
  var up = data.broker_up === true ? 'Broker up' : 'Broker offline';
201
- var cnt = data.peer_count != null
202
- ? data.peer_count + ' peer' + (data.peer_count === 1 ? '' : 's')
202
+ var remoteCount = data.remote_peer_count != null ? data.remote_peer_count : data.peer_count;
203
+ var cnt = remoteCount != null
204
+ ? remoteCount + ' remote peer' + (remoteCount === 1 ? '' : 's')
203
205
  : '';
206
+ var localCount = data.local_session_count || 0;
204
207
  var upt = data.uptime_s != null ? 'uptime ' + fmtUptime(data.uptime_s) : '';
205
208
  var parts = [up];
206
209
  if (cnt) parts.push(cnt);
210
+ if (localCount) parts.push(localCount + ' local session' + (localCount === 1 ? '' : 's'));
207
211
  if (upt) parts.push(upt);
208
212
  el.textContent = 'Agents sharing this memory mesh on your machine and LAN. ' +
209
213
  parts.join(' · ') + '. Peers go stale after 5 min without a heartbeat, dead after 30.';
@@ -236,10 +240,10 @@
236
240
  'justify-content:center;margin-bottom:16px;color:var(--fg-3)">' +
237
241
  meshEmptyIcon() +
238
242
  '</div>' +
239
- '<h3 style="font-size:15px;margin-bottom:6px">No peers connected</h3>' +
243
+ '<h3 style="font-size:15px;margin-bottom:6px">No mesh sessions connected</h3>' +
240
244
  '<p style="font-size:12.5px;color:var(--fg-2);' +
241
245
  'max-width:30ch;line-height:1.5">' +
242
- 'Start another agent session. Peers register via the' +
246
+ 'Start another agent session or connect a trusted LAN peer. Sessions register via the' +
243
247
  ' <code>mesh_summary</code> MCP tool.' +
244
248
  '</p>' +
245
249
  '</div>';
@@ -253,6 +257,9 @@
253
257
  var pidFrag = escapeHtml((p.peer_id || '').slice(0, 12));
254
258
  var summary = escapeHtml(p.summary || 'No summary');
255
259
  var atype = escapeHtml(p.agent_type || 'unknown');
260
+ var isLocal = String(p.host || '').toLowerCase() === '127.0.0.1' ||
261
+ String(p.host || '').toLowerCase() === 'localhost' || String(p.host || '') === '::1';
262
+ var scopeLabel = isLocal ? 'local session' : 'remote peer';
256
263
  var ago = escapeHtml(timeAgo(p.last_heartbeat));
257
264
  var proj = escapeHtml(p.project_path || '~');
258
265
  var port = p.port ? ' · :' + p.port : '';
@@ -274,7 +281,7 @@
274
281
  '<div style="flex:1;min-width:0">' +
275
282
  '<h3 style="font-size:15px;white-space:nowrap;overflow:hidden;' +
276
283
  'text-overflow:ellipsis">' + name + '</h3>' +
277
- '<span class="mono dim" style="font-size:11px">' + pidFrag + '</span>' +
284
+ '<span class="mono dim" style="font-size:11px">' + pidFrag + ' · ' + scopeLabel + '</span>' +
278
285
  '</div>' +
279
286
  '<span class="badge ' + badgeCls(st) + '">' +
280
287
  '<span class="dot"></span>' + escapeHtml(st) +
@@ -424,15 +431,22 @@
424
431
 
425
432
  // ── Data loaders ──────────────────────────────────────────────────────────
426
433
  function loadAll() {
434
+ var requestSeq = _requestSeq + 1;
435
+ _requestSeq = requestSeq;
427
436
  meshFetch('/mesh/status')
428
437
  .then(function (r) { return r.ok ? r.json() : null; })
429
438
  .catch(function () { return null; })
430
- .then(renderPageDesc);
439
+ .then(function (data) {
440
+ if (_requestSeq === requestSeq) renderPageDesc(data);
441
+ });
431
442
 
432
- meshFetch('/mesh/peers')
443
+ // The broker distinguishes remote LAN peers from local agent sessions.
444
+ // Both are real mesh participants and must be visible to operators.
445
+ meshFetch('/mesh/peers?view=all')
433
446
  .then(function (r) { return r.ok ? r.json() : { peers: [] }; })
434
447
  .catch(function () { return { peers: [] }; })
435
448
  .then(function (d) {
449
+ if (_requestSeq !== requestSeq) return;
436
450
  _peers = Array.isArray(d && d.peers) ? d.peers : [];
437
451
  renderPeers(_peers);
438
452
  // If the currently selected peer has expired, deselect
@@ -30,23 +30,67 @@
30
30
  var _tokenCache = null;
31
31
 
32
32
  /* ── Auth helper ──────────────────────────────────────────── */
33
- function getToken() {
33
+ function tokenError(message) {
34
+ var error = new Error(message);
35
+ error.code = 'install_token_missing';
36
+ return error;
37
+ }
38
+ function getToken(refresh) {
39
+ if (refresh) _tokenCache = null;
34
40
  if (_tokenCache) return Promise.resolve(_tokenCache);
35
41
  return fetch('/internal/token')
36
- .then(function (r) { return r.json(); })
37
- .then(function (d) { _tokenCache = d.token || ''; return _tokenCache; })
38
- .catch(function () { return ''; });
42
+ .then(function (response) {
43
+ if (!response.ok) throw new Error('Could not refresh dashboard credential');
44
+ return response.json();
45
+ })
46
+ .then(function (data) {
47
+ var token = data.token || '';
48
+ if (!token) throw tokenError('Dashboard credential is empty');
49
+ _tokenCache = token;
50
+ return token;
51
+ });
52
+ }
53
+ function sendAuthenticated(url, method, body, token) {
54
+ return fetch(url, {
55
+ method: method,
56
+ headers: { 'Content-Type': 'application/json', 'X-Install-Token': token },
57
+ body: JSON.stringify(body)
58
+ });
39
59
  }
40
- /** PUT with X-Install-Token. Use for all new config endpoints. */
41
- function authPut(url, body) {
42
- return getToken().then(function (tok) {
43
- return fetch(url, {
44
- method: 'PUT',
45
- headers: { 'Content-Type': 'application/json', 'X-Install-Token': tok },
46
- body: JSON.stringify(body)
60
+ function requireSuccess(response) {
61
+ if (response.ok) return Promise.resolve(response);
62
+ var details = typeof response.json === 'function'
63
+ ? response.json().catch(function () { return {}; })
64
+ : Promise.resolve({});
65
+ return details.then(function (data) {
66
+ var message = data.error || data.detail || data.message ||
67
+ ('Request failed with status ' + response.status);
68
+ var error = new Error(message);
69
+ error.status = response.status;
70
+ throw error;
71
+ });
72
+ }
73
+ /** Authenticated dashboard mutation; callers must re-read daemon truth on success. */
74
+ function authRequest(url, method, body) {
75
+ function attempt(refresh) {
76
+ return getToken(refresh).then(function (token) {
77
+ return sendAuthenticated(url, method, body, token);
47
78
  });
79
+ }
80
+ return attempt(false).then(function (response) {
81
+ if (response.status === 401 || response.status === 403) {
82
+ return attempt(true).then(requireSuccess);
83
+ }
84
+ return requireSuccess(response);
85
+ }, function (error) {
86
+ if (error && error.code === 'install_token_missing') {
87
+ return attempt(true).then(requireSuccess);
88
+ }
89
+ throw error;
48
90
  });
49
91
  }
92
+ function authPut(url, body) { return authRequest(url, 'PUT', body); }
93
+ function authPost(url, body) { return authRequest(url, 'POST', body); }
50
94
 
51
95
  /* ── Helpers ──────────────────────────────────────────────── */
52
96
  function esc(s) {
@@ -270,9 +314,7 @@
270
314
  var ep = q('endpoint') ? q('endpoint').value : '';
271
315
  if (k) { body.api_key = k; }
272
316
  if (ep) { body.base_url = ep; body.endpoint = ep; }
273
- fetch('/api/v3/mode/set', {
274
- method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(body)
275
- }).then(function (r) { return r.json(); })
317
+ authPost('/api/v3/mode/set', body).then(function (r) { return r.json(); })
276
318
  .then(function () { setSt('mode', 'Saved', true); toast('Mode saved'); loadMode(); })
277
319
  .catch(function () { setSt('mode', 'Error', false); toast('Mode save failed', true); });
278
320
  }
@@ -348,10 +390,8 @@
348
390
  embedding_dimension: parseInt(q('emb-dim') ? q('emb-dim').value : '768') || 768,
349
391
  embedding_key: q('emb-key') ? q('emb-key').value : '' }
350
392
  : { embedding_provider:'default' };
351
- fetch('/api/v3/mode/set', {
352
- method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(body)
353
- }).then(function (r) { return r.json(); })
354
- .then(function () { setSt('emb', 'Saved', true); toast('Embedding config saved'); })
393
+ authPost('/api/v3/mode/set', body).then(function (r) { return r.json(); })
394
+ .then(function () { setSt('emb', 'Saved', true); toast('Embedding config saved'); loadEmb(); })
355
395
  .catch(function () { setSt('emb', 'Error', false); toast('Embedding save failed', true); });
356
396
  }
357
397
  function testEmb() {
@@ -418,6 +458,7 @@
418
458
  setSt('stor', 'Saved', true);
419
459
  toast('Storage backends saved');
420
460
  showRestartNote('stor', !!d.restart_required);
461
+ loadStorage();
421
462
  }).catch(function () { setSt('stor', 'Error', false); toast('Storage save failed', true); });
422
463
  }
423
464
 
@@ -442,6 +483,7 @@
442
483
  }).then(function (r) { return r.json(); })
443
484
  .then(function (d) {
444
485
  setSt('scope', d.success !== false ? 'Applied' : esc(d.error || 'Error'), d.success !== false);
486
+ if (d.success !== false) loadScope();
445
487
  })
446
488
  .catch(function () { setSt('scope', 'Error', false); toast('Scope save failed', true); });
447
489
  }
@@ -481,9 +523,9 @@
481
523
  capture_decisions: swDec.classList.contains('on'),
482
524
  capture_bugs: swBug.classList.contains('on')
483
525
  });
484
- fetch('/api/v3/auto-capture/config', {
485
- method:'PUT', headers:{'Content-Type':'application/json'}, body:JSON.stringify(body)
486
- }).catch(function () { toast('Auto-capture save failed', true); });
526
+ authPut('/api/v3/auto-capture/config', body)
527
+ .then(function (r) { if (!r.ok) throw new Error('Save failed'); loadCapture(); })
528
+ .catch(function () { toast('Auto-capture save failed', true); });
487
529
  }
488
530
  sw.addEventListener('od-toggle', save);
489
531
  swDec.addEventListener('od-toggle', save);
@@ -516,13 +558,11 @@
516
558
  var sw = makeSwitch('rec-en', true);
517
559
  var swSess = makeSwitch('rec-sess', true);
518
560
  function save() {
519
- fetch('/api/v3/auto-recall/config', {
520
- method:'PUT', headers:{'Content-Type':'application/json'},
521
- body:JSON.stringify({
561
+ authPut('/api/v3/auto-recall/config', {
522
562
  enabled: sw.classList.contains('on'),
523
563
  on_session_start: swSess.classList.contains('on')
524
- })
525
- }).catch(function () { toast('Auto-recall save failed', true); });
564
+ }).then(function (r) { if (!r.ok) throw new Error('Save failed'); loadRecall(); })
565
+ .catch(function () { toast('Auto-recall save failed', true); });
526
566
  }
527
567
  sw.addEventListener('od-toggle', save);
528
568
  swSess.addEventListener('od-toggle', save);
@@ -552,15 +592,12 @@
552
592
  var arSw = makeSwitch('ai-actr', false);
553
593
  var st = makeSt('ai');
554
594
  function save() {
555
- fetch('/api/v3/auto-invoke/config', {
556
- method:'PUT', headers:{'Content-Type':'application/json'},
557
- body:JSON.stringify({
595
+ authPut('/api/v3/auto-invoke/config', {
558
596
  enabled: sw.classList.contains('on'),
559
597
  min_score: parseFloat(q('ai-fok') ? q('ai-fok').value : '0.12'),
560
598
  act_r_mode: arSw.classList.contains('on')
561
- })
562
- }).then(function (r) { return r.json(); })
563
- .then(function () { setSt('ai', 'Saved', true); })
599
+ }).then(function (r) { return r.json(); })
600
+ .then(function () { setSt('ai', 'Saved', true); loadInvoke(); })
564
601
  .catch(function () { toast('Auto-invoke save failed', true); });
565
602
  }
566
603
  sw.addEventListener('od-toggle', save);
@@ -602,6 +639,7 @@
602
639
  { fontSize:'12px', color:'var(--fg-2)', fontFamily:'var(--font-mono)' });
603
640
  var saveBtn = makeBtn('forg-save', 'Save Forgetting', true);
604
641
  var st = makeSt('forg');
642
+ var restNote = makeRestartNote('forg');
605
643
  saveBtn.addEventListener('click', saveForgetting);
606
644
  onSw.addEventListener('od-toggle', saveForgetting);
607
645
  immSw.addEventListener('od-toggle', saveForgetting);
@@ -615,7 +653,7 @@
615
653
  makeRow('Forget threshold', 'Memories below this strength are permanently deleted', 'forgetting.forget_threshold', fgtR),
616
654
  makeRow('Run every (minutes)', 'How often the forgetting engine runs in the background', 'forgetting.scheduler_interval_minutes', intInp),
617
655
  makeRow('Memory zone stats', '', 'forgetting.stats', statsEl),
618
- makeRow('Actions', '', '', bRow(saveBtn, st))
656
+ makeRow('Actions', '', '', bRow(saveBtn, st, restNote))
619
657
  ]);
620
658
  }
621
659
  function loadForgetting() {
@@ -647,6 +685,7 @@
647
685
  }
648
686
  function saveForgetting() {
649
687
  setSt('forg', 'Saving…', null);
688
+ showRestartNote('forg', false);
650
689
  authPut('/api/v3/forgetting/config', {
651
690
  enabled: q('forg-en') ? q('forg-en').classList.contains('on') : false,
652
691
  core_memory_immune: q('forg-imm') ? q('forg-imm').classList.contains('on') : true,
@@ -654,7 +693,12 @@
654
693
  forget_threshold: parseFloat(q('forg-fgt') ? q('forg-fgt').value : '0.05'),
655
694
  scheduler_interval_minutes: parseInt(q('forg-int') ? q('forg-int').value : '30') || 30
656
695
  }).then(function (r) { return r.json(); })
657
- .then(function () { setSt('forg', 'Saved', true); toast('Forgetting settings saved'); })
696
+ .then(function (d) {
697
+ setSt('forg', 'Saved', true);
698
+ toast('Forgetting settings saved');
699
+ showRestartNote('forg', !!d.restart_required);
700
+ loadForgetting();
701
+ })
658
702
  .catch(function () { setSt('forg', 'Error', false); toast('Forgetting save failed', true); });
659
703
  }
660
704
 
@@ -673,7 +717,7 @@
673
717
  var st = makeSt('evo');
674
718
  sw.addEventListener('od-toggle', function () {
675
719
  var on = sw.classList.contains('on');
676
- fetch(on ? '/api/evolution/enable' : '/api/evolution/disable', { method:'POST' })
720
+ authPost(on ? '/api/evolution/enable' : '/api/evolution/disable', {})
677
721
  .then(function (r) { return r.json(); })
678
722
  .then(function (d) {
679
723
  if (d.ok === false) {
@@ -692,11 +736,9 @@
692
736
  });
693
737
  saveBtn.addEventListener('click', function () {
694
738
  setSt('evo', 'Saving…', null);
695
- fetch('/api/evolution/config', {
696
- method:'POST', headers:{'Content-Type':'application/json'},
697
- body:JSON.stringify({ backend: q('evo-bk') ? q('evo-bk').value : 'auto' })
698
- }).then(function (r) { return r.json(); })
699
- .then(function () { setSt('evo', 'Saved', true); })
739
+ authPost('/api/evolution/config', { backend: q('evo-bk') ? q('evo-bk').value : 'auto' })
740
+ .then(function (r) { return r.json(); })
741
+ .then(function (d) { if (d.ok === false) throw new Error(d.error || 'Save failed'); setSt('evo', 'Saved', true); loadEvolution(); })
700
742
  .catch(function () { setSt('evo', 'Error', false); toast('Evolution save failed', true); });
701
743
  });
702
744
  return makeGrp('Skill Evolution', 'skill', [
@@ -731,7 +773,7 @@
731
773
  saveBtn.addEventListener('click', saveBackupConfig);
732
774
  nowBtn.addEventListener('click', function () {
733
775
  setSt('bk', 'Creating…', null);
734
- fetch('/api/backup/create', { method:'POST' }).then(function (r) { return r.json(); })
776
+ authPost('/api/backup/create', {}).then(function (r) { return r.json(); })
735
777
  .then(function (d) {
736
778
  setSt('bk', d.success ? 'Done: ' + esc(d.filename || '') : 'Failed', d.success);
737
779
  toast(d.success ? 'Backup created' : 'Backup failed', !d.success);
@@ -768,15 +810,12 @@
768
810
  }
769
811
  function saveBackupConfig() {
770
812
  setSt('bk', 'Saving…', null);
771
- fetch('/api/backup/configure', {
772
- method:'POST', headers:{'Content-Type':'application/json'},
773
- body:JSON.stringify({
813
+ authPost('/api/backup/configure', {
774
814
  interval_hours: parseInt(q('bk-int') ? q('bk-int').value : '168'),
775
815
  max_backups: parseInt(q('bk-max') ? q('bk-max').value : '10'),
776
816
  enabled: q('bk-en') ? q('bk-en').classList.contains('on') : true
777
- })
778
- }).then(function (r) { return r.json(); })
779
- .then(function () { setSt('bk', 'Saved', true); toast('Backup settings saved'); })
817
+ }).then(function (r) { return r.json(); })
818
+ .then(function (d) { if (d.success === false) throw new Error(d.message || 'Save failed'); setSt('bk', 'Saved', true); toast('Backup settings saved'); loadBackup(); })
780
819
  .catch(function () { setSt('bk', 'Error', false); toast('Backup save failed', true); });
781
820
  }
782
821
 
@@ -787,15 +826,24 @@
787
826
  function buildMesh() {
788
827
  var sw = makeSwitch('mesh-en', false);
789
828
  var st = makeSt('mesh');
829
+ var restNote = makeRestartNote('mesh');
790
830
  sw.addEventListener('od-toggle', function () {
831
+ showRestartNote('mesh', false);
791
832
  authPut('/api/v3/mesh/config', { enabled: sw.classList.contains('on') })
792
833
  .then(function (r) { return r.json(); })
793
- .then(function () { setSt('mesh', 'Saved', true); })
794
- .catch(function () { setSt('mesh', 'Error', false); toast('Mesh save failed', true); });
834
+ .then(function (d) {
835
+ setSt('mesh', 'Saved', true);
836
+ showRestartNote('mesh', !!d.restart_required);
837
+ })
838
+ .catch(function () {
839
+ setSt('mesh', 'Error', false);
840
+ toast('Mesh save failed', true);
841
+ loadMesh();
842
+ });
795
843
  });
796
844
  return makeGrp('Mesh Network', 'mesh', [
797
845
  makeRow('Enable mesh sync', 'Synchronise memories across devices on the same local network', 'mesh.enabled', sw),
798
- makeRow('Status', '', '', st)
846
+ makeRow('Status', '', '', bRow(st, restNote))
799
847
  ]);
800
848
  }
801
849
  function loadMesh() {
@@ -817,6 +865,7 @@
817
865
  var pmR = makeRange('trust-pm', '0', '1', '0.01', '0.5');
818
866
  var saveBtn = makeBtn('trust-save', 'Save Trust', true);
819
867
  var st = makeSt('trust');
868
+ var restNote = makeRestartNote('trust');
820
869
  saveBtn.addEventListener('click', saveTrust);
821
870
  twSw.addEventListener('od-toggle', saveTrust);
822
871
  fpSw.addEventListener('od-toggle', saveTrust);
@@ -825,7 +874,7 @@
825
874
  makeRow('Weight recall by trust', 'Higher-trust sources appear first in recall results', 'trust.use_trust_weighting', twSw),
826
875
  makeRow('Prefer first-party agents', 'Your own Claude/Gemini sessions rank higher than third-party', 'trust.trust_first_party', fpSw),
827
876
  makeRow('Promotion min trust', 'Min trust score required to promote a memory to long-term storage', 'trust.promotion_min_trust', pmR),
828
- makeRow('Actions', '', '', bRow(saveBtn, st))
877
+ makeRow('Actions', '', '', bRow(saveBtn, st, restNote))
829
878
  ]);
830
879
  }
831
880
  function loadTrust() {
@@ -842,12 +891,17 @@
842
891
  }
843
892
  function saveTrust() {
844
893
  setSt('trust', 'Saving…', null);
894
+ showRestartNote('trust', false);
845
895
  authPut('/api/v3/trust/config', {
846
896
  use_trust_weighting: q('trust-tw') ? q('trust-tw').classList.contains('on') : true,
847
897
  trust_first_party: q('trust-fp') ? q('trust-fp').classList.contains('on') : false,
848
898
  promotion_min_trust: parseFloat(q('trust-pm') ? q('trust-pm').value : '0.5')
849
899
  }).then(function (r) { return r.json(); })
850
- .then(function () { setSt('trust', 'Saved', true); })
900
+ .then(function (d) {
901
+ setSt('trust', 'Saved', true);
902
+ showRestartNote('trust', !!d.restart_required);
903
+ loadTrust();
904
+ })
851
905
  .catch(function () { setSt('trust', 'Error', false); toast('Trust save failed', true); });
852
906
  }
853
907
 
@@ -899,7 +953,7 @@
899
953
  read: parseInt(q('rl-read') ? q('rl-read').value : '300') || 300,
900
954
  window: parseInt(q('rl-window') ? q('rl-window').value : '60') || 60
901
955
  }).then(function (r) { return r.json(); })
902
- .then(function (d) { setSt('rl', 'Saved', true); _rlSetLb(d); })
956
+ .then(function (d) { setSt('rl', 'Saved', true); _rlSetLb(d); loadRateLimit(); })
903
957
  .catch(function () { setSt('rl', 'Error', false); toast('Rate limit save failed', true); });
904
958
  }
905
959
 
@@ -952,6 +1006,7 @@
952
1006
  setSt('dmn', 'Saved', true);
953
1007
  toast('Daemon settings saved');
954
1008
  showRestartNote('dmn', !!d.restart_required);
1009
+ loadDaemon();
955
1010
  }).catch(function () { setSt('dmn', 'Error', false); toast('Daemon save failed', true); });
956
1011
  }
957
1012
 
@@ -992,7 +1047,7 @@
992
1047
  var headDiv = el('div', null, { marginBottom:'22px' });
993
1048
  headDiv.appendChild(el('h2', { text:'Settings' },
994
1049
  { fontSize:'22px', fontWeight:'650', letterSpacing:'-0.02em' }));
995
- headDiv.appendChild(el('p', { text:'Every SuperLocalMemory configuration, grouped and searchable — no config files to edit by hand. Changes hot-reload the local daemon.' },
1050
+ headDiv.appendChild(el('p', { text:'Every SuperLocalMemory configuration, grouped and searchable — no config files to edit by hand. Settings show when a daemon restart is required.' },
996
1051
  { color:'var(--fg-2)', marginTop:'5px', maxWidth:'62ch' }));
997
1052
  hub.appendChild(headDiv);
998
1053
 
@@ -1022,15 +1077,14 @@
1022
1077
  var discardBtn = el('button', { type:'button' }); discardBtn.className = 'btn ghost';
1023
1078
  discardBtn.textContent = 'Discard';
1024
1079
  var syncBadge = el('span', null, { alignSelf:'center', marginLeft:'auto' });
1025
- syncBadge.className = 'badge ok';
1026
- syncBadge.innerHTML = '<span class="dot"></span> All synced';
1080
+ syncBadge.className = 'badge';
1081
+ syncBadge.innerHTML = '<span class="dot"></span> Ready';
1027
1082
  saveAllBtn.addEventListener('click', function () {
1028
1083
  ['mode-save','emb-save','stor-save','forg-save','trust-save','rl-save','evo-save','bk-save','dmn-save']
1029
1084
  .forEach(function (i) { var b = q(i); if (b) b.click(); });
1030
1085
  saveBackupConfig();
1031
- syncBadge.className = 'badge ok';
1032
- syncBadge.innerHTML = '<span class="dot"></span> Saved';
1033
- setTimeout(function () { syncBadge.innerHTML = '<span class="dot"></span> All synced'; }, 3000);
1086
+ syncBadge.className = 'badge';
1087
+ syncBadge.innerHTML = '<span class="dot"></span> Check each section';
1034
1088
  });
1035
1089
  discardBtn.addEventListener('click', function () { loadAll(); });
1036
1090
  foot.appendChild(saveAllBtn); foot.appendChild(discardBtn); foot.appendChild(syncBadge);