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
@@ -94,6 +94,32 @@
94
94
  grp.items.forEach(function (it) { NAV_MAP[it.k] = it; });
95
95
  });
96
96
 
97
+ // OD panes use a short in-memory stale-while-revalidate window. Navigation
98
+ // inside the window is a pure DOM switch; after it expires, the shell
99
+ // refreshes a hidden replacement pane and keeps the mounted pane visible
100
+ // until the replacement settles. Values can be overridden before this
101
+ // script loads to make the lifecycle deterministic in the Node UI harness.
102
+ var PANE_CACHE_TTL_MS = Number(window.SLM_PANE_CACHE_TTL_MS) || 15000;
103
+ var PANE_REFRESH_QUIET_MS = Number(window.SLM_PANE_REFRESH_QUIET_MS);
104
+ if (!Number.isFinite(PANE_REFRESH_QUIET_MS) || PANE_REFRESH_QUIET_MS < 0) {
105
+ PANE_REFRESH_QUIET_MS = 150;
106
+ }
107
+ var PANE_REFRESH_MAX_MS = 30000;
108
+ var paneLoadState = {};
109
+ var paneRefreshTail = Promise.resolve();
110
+
111
+ function getPaneLoadState(paneId) {
112
+ if (!paneLoadState[paneId]) {
113
+ paneLoadState[paneId] = {
114
+ mounted: false,
115
+ loadedAt: 0,
116
+ refreshing: false,
117
+ generation: 0,
118
+ };
119
+ }
120
+ return paneLoadState[paneId];
121
+ }
122
+
97
123
  /* ================================================================
98
124
  Brand links
99
125
  ================================================================ */
@@ -104,7 +130,6 @@
104
130
  qualixar: 'https://qualixar.com',
105
131
  author: 'https://varunpratap.com',
106
132
  license: 'https://github.com/qualixar/superlocalmemory/blob/main/LICENSE',
107
- stars: '2,431',
108
133
  };
109
134
 
110
135
  /* ================================================================
@@ -201,6 +226,14 @@
201
226
  // Show target pane
202
227
  var pane = document.getElementById(paneId);
203
228
  if (pane) pane.classList.add('show', 'active');
229
+ // During a stale refresh the canonical pane is rendered off-screen and
230
+ // the previous pane remains as the visual snapshot. Keep that snapshot in
231
+ // sync with navigation so returning mid-refresh never exposes a blank pane.
232
+ document.querySelectorAll('[data-slm-stale-for]').forEach(function (snapshot) {
233
+ if (snapshot.dataset.slmStaleFor === paneId) {
234
+ snapshot.classList.add('show', 'active');
235
+ }
236
+ });
204
237
 
205
238
  // Update sidebar active state
206
239
  document.querySelectorAll('.nav-link[data-tab]').forEach(function (link) {
@@ -216,10 +249,16 @@
216
249
  if (headEl) headEl.textContent = item.t;
217
250
  }
218
251
 
219
- // Dispatch Bootstrap shown.bs.tab event for backward compatibility.
220
- // brain.js, dashboard.js, etc. listen on the hidden tab buttons.
252
+ // Mount the owning renderer before dispatching any legacy event. Returning
253
+ // to a mounted OD pane preserves its DOM/data instead of re-querying every
254
+ // API and showing skeletons again.
255
+ var handledByShell = triggerTabLoad(paneId);
256
+
257
+ // Dispatch Bootstrap shown.bs.tab only for panes not owned by a shell
258
+ // loader (currently Dashboard). OD-owned panes and direct legacy fallbacks
259
+ // have already loaded exactly once above.
221
260
  var tabBtn = document.getElementById(paneId.replace('-pane', '-tab'));
222
- if (tabBtn) {
261
+ if (tabBtn && !handledByShell) {
223
262
  try {
224
263
  tabBtn.dispatchEvent(new Event('shown.bs.tab', { bubbles: true }));
225
264
  } catch (e) {}
@@ -233,8 +272,6 @@
233
272
  var contentEl = document.getElementById('main-content');
234
273
  if (contentEl) contentEl.scrollTo({ top: 0, behavior: 'instant' });
235
274
 
236
- // Fire lazy data loaders
237
- triggerTabLoad(paneId);
238
275
  // Deferred retry ONLY if the first render produced nothing (a pane that ran
239
276
  // before it was visible/sized). Re-running unconditionally used to wipe and
240
277
  // rebuild every pane 500ms after each switch — the visible "cards reload"
@@ -257,6 +294,152 @@
257
294
  /* ================================================================
258
295
  Lazy data loaders — same mapping as ng-shell.js triggerTabLoad
259
296
  ================================================================ */
297
+ function queueOdPaneRefresh(tabId, fnName) {
298
+ var state = getPaneLoadState(tabId);
299
+ if (state.refreshing) return;
300
+ state.refreshing = true;
301
+ var generation = state.generation;
302
+
303
+ // Serialising the short handoff prevents nested global fetch wrappers when
304
+ // a user races across several stale panes. Each pane still remains usable
305
+ // from its mounted DOM while it waits its turn.
306
+ paneRefreshTail = paneRefreshTail
307
+ .catch(function () {})
308
+ .then(function () {
309
+ if (state.generation !== generation) return;
310
+ return refreshOdPane(tabId, fnName, state, generation);
311
+ })
312
+ .catch(function (err) {
313
+ console.error('OD background refresh failed for ' + tabId + ':', err);
314
+ })
315
+ .then(function () {
316
+ state.refreshing = false;
317
+ });
318
+ }
319
+
320
+ function refreshOdPane(tabId, fnName, state, generation) {
321
+ var oldPane = document.getElementById(tabId);
322
+ if (!oldPane || typeof window[fnName] !== 'function') return Promise.resolve();
323
+
324
+ var snapshotId = tabId + '--stale-' + String(generation);
325
+ var freshPane = oldPane.cloneNode(false);
326
+ var originalDisplay = freshPane.style.display;
327
+ oldPane.id = snapshotId;
328
+ oldPane.dataset.slmStaleFor = tabId;
329
+ freshPane.id = tabId;
330
+ freshPane.style.display = 'none';
331
+ freshPane.dataset.slmOdRenderer = fnName;
332
+ oldPane.parentNode.insertBefore(freshPane, oldPane);
333
+
334
+ return new Promise(function (resolve) {
335
+ var originalFetch = window.fetch;
336
+ var pending = 0;
337
+ var idleTimer = null;
338
+ var maxTimer = null;
339
+ var finished = false;
340
+ var observer = typeof MutationObserver === 'function'
341
+ ? new MutationObserver(scheduleIdle)
342
+ : null;
343
+
344
+ function restoreFetch() {
345
+ if (window.fetch === trackedFetch) window.fetch = originalFetch;
346
+ }
347
+
348
+ function restoreSnapshot() {
349
+ if (freshPane.parentNode) freshPane.parentNode.removeChild(freshPane);
350
+ oldPane.id = tabId;
351
+ delete oldPane.dataset.slmStaleFor;
352
+ // An explicit mutation/profile invalidation wins over an in-flight
353
+ // refresh. Preserve the visual snapshot, but leave it unmounted so the
354
+ // next visit cannot accidentally re-age invalid data as a cache hit.
355
+ if (state.generation !== generation) {
356
+ delete oldPane.dataset.slmOdRenderer;
357
+ }
358
+ }
359
+
360
+ function finish(commit) {
361
+ if (finished) return;
362
+ finished = true;
363
+ if (idleTimer) clearTimeout(idleTimer);
364
+ if (maxTimer) clearTimeout(maxTimer);
365
+ if (observer) observer.disconnect();
366
+ restoreFetch();
367
+
368
+ if (commit && state.generation === generation) {
369
+ if (oldPane.parentNode) oldPane.parentNode.removeChild(oldPane);
370
+ freshPane.style.display = originalDisplay;
371
+ freshPane.dataset.slmOdRenderer = fnName;
372
+ state.mounted = true;
373
+ state.loadedAt = Date.now();
374
+ } else {
375
+ restoreSnapshot();
376
+ }
377
+ resolve();
378
+ }
379
+
380
+ function scheduleIdle() {
381
+ if (finished || pending > 0) return;
382
+ if (idleTimer) clearTimeout(idleTimer);
383
+ idleTimer = setTimeout(function () { finish(true); }, PANE_REFRESH_QUIET_MS);
384
+ }
385
+
386
+ function trackPromise(promise) {
387
+ pending += 1;
388
+ if (idleTimer) {
389
+ clearTimeout(idleTimer);
390
+ idleTimer = null;
391
+ }
392
+ Promise.resolve(promise).then(settled, settled);
393
+ return promise;
394
+ }
395
+
396
+ function settled() {
397
+ pending = Math.max(0, pending - 1);
398
+ scheduleIdle();
399
+ }
400
+
401
+ function patchResponse(response) {
402
+ ['json', 'text', 'blob', 'arrayBuffer', 'formData'].forEach(function (name) {
403
+ if (!response || typeof response[name] !== 'function') return;
404
+ var bodyReader = response[name].bind(response);
405
+ try {
406
+ response[name] = function () {
407
+ return trackPromise(bodyReader());
408
+ };
409
+ } catch (e) {}
410
+ });
411
+ return response;
412
+ }
413
+
414
+ function trackedFetch() {
415
+ var result;
416
+ try {
417
+ result = originalFetch.apply(window, arguments);
418
+ } catch (err) {
419
+ scheduleIdle();
420
+ throw err;
421
+ }
422
+ var patched = Promise.resolve(result).then(patchResponse);
423
+ trackPromise(patched);
424
+ return patched;
425
+ }
426
+
427
+ if (observer) {
428
+ observer.observe(freshPane, { childList: true, subtree: true, characterData: true });
429
+ }
430
+ if (typeof originalFetch === 'function') window.fetch = trackedFetch;
431
+ maxTimer = setTimeout(function () { finish(false); }, PANE_REFRESH_MAX_MS);
432
+
433
+ try {
434
+ window[fnName](freshPane);
435
+ scheduleIdle();
436
+ } catch (err) {
437
+ finish(false);
438
+ throw err;
439
+ }
440
+ });
441
+ }
442
+
260
443
  function triggerTabLoad(tabId) {
261
444
  var pane = document.getElementById(tabId);
262
445
  // Open Design takeover: if the pane's OD render module is loaded, it OWNS
@@ -265,80 +448,114 @@
265
448
  // only when the OD module for that pane isn't present yet.
266
449
  function od(fnName) {
267
450
  if (pane && typeof window[fnName] === 'function') {
268
- try { pane.innerHTML = ''; window[fnName](pane); return true; }
451
+ if (pane.dataset.slmOdRenderer === fnName && pane.children.length > 0) {
452
+ var cached = getPaneLoadState(tabId);
453
+ if (!cached.mounted) {
454
+ cached.mounted = true;
455
+ cached.loadedAt = Date.now();
456
+ } else if (Date.now() - cached.loadedAt >= PANE_CACHE_TTL_MS) {
457
+ queueOdPaneRefresh(tabId, fnName);
458
+ }
459
+ return true;
460
+ }
461
+ try {
462
+ pane.innerHTML = '';
463
+ window[fnName](pane);
464
+ pane.dataset.slmOdRenderer = fnName;
465
+ var mounted = getPaneLoadState(tabId);
466
+ mounted.mounted = true;
467
+ mounted.loadedAt = Date.now();
468
+ return true;
469
+ }
269
470
  catch (e) { console.error('OD render failed for ' + tabId + ':', e); }
270
471
  }
271
472
  return false;
272
473
  }
273
474
  switch (tabId) {
274
475
  case 'brain-pane':
275
- if (od('odRenderBrain')) break;
476
+ if (od('odRenderBrain')) return true;
276
477
  if (typeof loadBrain === 'function') loadBrain();
277
- break;
478
+ return true;
278
479
  case 'graph-pane':
279
- if (od('odRenderGraph')) break;
480
+ if (od('odRenderGraph')) return true;
280
481
  if (typeof loadGraph === 'function') loadGraph();
281
482
  if (typeof initMemoryChat === 'function' && !document.getElementById('chat-panel')) {
282
483
  initMemoryChat();
283
484
  }
284
485
  if (typeof loadClusters === 'function') loadClusters();
285
- break;
486
+ return true;
286
487
  case 'entities-pane':
287
- if (od('odRenderEntities')) break;
488
+ if (od('odRenderEntities')) return true;
288
489
  if (typeof loadEntityExplorer === 'function') loadEntityExplorer();
289
- break;
490
+ return true;
290
491
  case 'memories-pane':
291
- if (od('odRenderMemories')) break;
492
+ if (od('odRenderMemories')) return true;
292
493
  if (typeof loadMemories === 'function') loadMemories();
293
494
  if (typeof loadTimeline === 'function') loadTimeline();
294
- break;
495
+ return true;
295
496
  case 'health-pane':
296
- if (od('odRenderHealth')) break;
497
+ if (od('odRenderHealth')) return true;
297
498
  if (typeof loadHealthMonitor === 'function') loadHealthMonitor();
298
499
  if (typeof initEventStream === 'function') initEventStream();
299
500
  if (typeof loadEventStats === 'function') loadEventStats();
300
501
  if (typeof loadAgents === 'function') loadAgents();
301
502
  if (typeof loadIDEStatus === 'function') loadIDEStatus();
302
503
  if (typeof loadMathHealth === 'function') loadMathHealth();
303
- break;
504
+ return true;
304
505
  case 'operations-pane':
305
- if (od('odRenderOperations')) break;
506
+ if (od('odRenderOperations')) return true;
306
507
  if (typeof loadIngestionStatus === 'function') loadIngestionStatus();
307
508
  if (typeof loadLifecycle === 'function') loadLifecycle();
308
509
  if (typeof loadTrustDashboard === 'function') loadTrustDashboard();
309
510
  if (typeof loadCompliance === 'function') loadCompliance();
310
- break;
511
+ return true;
311
512
  case 'skills-pane':
312
- if (od('odRenderSkills')) break;
513
+ if (od('odRenderSkills')) return true;
313
514
  if (typeof loadSkillEvolution === 'function') loadSkillEvolution();
314
- break;
515
+ return true;
315
516
  case 'mesh-pane':
316
- if (od('odRenderMesh')) break;
517
+ if (od('odRenderMesh')) return true;
317
518
  if (typeof loadMeshPeers === 'function') loadMeshPeers();
318
- break;
519
+ return true;
319
520
  case 'settings-pane':
320
- if (od('odRenderSettings')) break;
521
+ if (od('odRenderSettings')) return true;
321
522
  if (typeof loadSettings === 'function') loadSettings();
322
523
  if (typeof loadModeSettings === 'function') loadModeSettings();
323
524
  if (typeof loadAutoSettings === 'function') loadAutoSettings();
324
525
  if (typeof updateModeUI === 'function') updateModeUI();
325
- break;
526
+ return true;
326
527
  case 'optimize-pane':
327
- if (od('odRenderOptimize')) break;
528
+ if (od('odRenderOptimize')) return true;
328
529
  if (typeof initOptimizeTab === 'function') initOptimizeTab();
329
- break;
530
+ return true;
330
531
  case 'agents-pane':
331
- if (od('odRenderAgents')) break;
332
- break;
532
+ od('odRenderAgents');
533
+ return true;
333
534
  case 'mcp-pane':
334
535
  od('odRenderMcp');
335
- break;
536
+ return true;
336
537
  case 'backup-pane':
337
538
  od('odRenderBackup');
338
- break;
539
+ return true;
339
540
  }
541
+ return false;
340
542
  }
341
543
 
544
+ // Mutating actions and profile switches can invalidate cached panes without
545
+ // tearing down unrelated navigation state. The next visit remounts only the
546
+ // affected pane; no persistent browser storage is used.
547
+ window.slmInvalidatePanes = function (paneIds) {
548
+ var ids = Array.isArray(paneIds) ? paneIds : Object.keys(NAV_MAP);
549
+ ids.forEach(function (id) {
550
+ var pane = document.getElementById(id);
551
+ if (pane) delete pane.dataset.slmOdRenderer;
552
+ var state = getPaneLoadState(id);
553
+ state.generation += 1;
554
+ state.mounted = false;
555
+ state.loadedAt = 0;
556
+ });
557
+ };
558
+
342
559
  /* ================================================================
343
560
  Hash routing: handles both pane IDs and section anchors within panes
344
561
  ================================================================ */
@@ -430,8 +647,7 @@
430
647
  starLink.title = 'Star SuperLocalMemory on GitHub';
431
648
  starLink.innerHTML =
432
649
  svg('github', 'gh') +
433
- "<span class='lbl'>Star us on GitHub</span>" +
434
- "<span class='star-count'>" + svg('star') + LINKS.stars + '</span>';
650
+ "<span class='lbl'>Star us on GitHub</span>";
435
651
  if (themeBtn) topbar.insertBefore(starLink, themeBtn);
436
652
  else topbar.appendChild(starLink);
437
653
  }
@@ -10,6 +10,7 @@
10
10
  // GET /api/behavioral/assertions?category=skill_correlation&limit=20 → {assertions[]}
11
11
  // GET /api/behavioral/tool-events?tool_name=Skill&limit=500 → {events[]}
12
12
  //
13
+ // POST /api/evolution/config — called when an engine setting is saved
13
14
  // POST /api/evolution/enable — called when user toggles the master switch ON
14
15
  // POST /api/evolution/run — called when user clicks "Run one evolution cycle"
15
16
  //
@@ -27,6 +28,16 @@
27
28
  // ======================================================================
28
29
  // Fetch
29
30
  // ======================================================================
31
+ var tokenPromise = null;
32
+ function getToken() {
33
+ if (!tokenPromise) {
34
+ tokenPromise = fetch('/internal/token', { credentials: 'same-origin' })
35
+ .then(function (r) { return r.ok ? r.json() : {}; })
36
+ .then(function (d) { return d.token || ''; });
37
+ }
38
+ return tokenPromise;
39
+ }
40
+
30
41
  function apiFetch(path) {
31
42
  return fetch(path, { credentials: 'same-origin' })
32
43
  .then(function (r) {
@@ -36,14 +47,16 @@
36
47
  }
37
48
 
38
49
  function apiPost(path, body) {
39
- return fetch(path, {
40
- method: 'POST',
41
- headers: { 'Content-Type': 'application/json' },
42
- credentials: 'same-origin',
43
- body: JSON.stringify(body || {}),
50
+ return getToken().then(function (token) {
51
+ return fetch(path, {
52
+ method: 'POST',
53
+ headers: { 'Content-Type': 'application/json', 'X-Install-Token': token },
54
+ credentials: 'same-origin',
55
+ body: JSON.stringify(body || {}),
56
+ });
44
57
  }).then(function (r) {
45
- if (!r.ok) throw new Error(path + ' → ' + r.status);
46
- return r.json();
58
+ if (!r.ok) throw new Error(path + ' → ' + r.status);
59
+ return r.json();
47
60
  });
48
61
  }
49
62
 
@@ -296,7 +309,7 @@
296
309
  return row;
297
310
  }
298
311
 
299
- // Master switch — wired to POST /api/evolution/enable
312
+ // Master switch — wired to POST /api/evolution/enable|disable
300
313
  var sw = EL('button', {
301
314
  className: 'switch' + (evolution.enabled ? ' on' : ''),
302
315
  type: 'button',
@@ -304,13 +317,7 @@
304
317
  sw.setAttribute('aria-label', 'Toggle evolution engine');
305
318
  sw.addEventListener('click', function () {
306
319
  var enabling = !sw.classList.contains('on');
307
- if (!enabling) {
308
- // TODO: No /api/evolution/disable endpoint — the HTTP API only exposes enable.
309
- // To disable via CLI: slm config set evolution.enabled false
310
- window.alert('To disable evolution, run in your terminal:\nslm config set evolution.enabled false');
311
- return;
312
- }
313
- apiPost('/api/evolution/enable', {})
320
+ apiPost(enabling ? '/api/evolution/enable' : '/api/evolution/disable', {})
314
321
  .then(function () { render(container); })
315
322
  .catch(function (err) {
316
323
  if (window.console) window.console.error('[od-skills] enable evolution:', err.message);
@@ -319,8 +326,13 @@
319
326
  });
320
327
  cb.appendChild(ctlRow('Enabled', 'Master switch (off by default)', sw));
321
328
 
322
- cb.appendChild(ctlRow('Backend', 'detection: auto',
323
- EL('span', { className: 'mono', text: evolution.backend || 'none' })));
329
+ var backend = EL('select', { 'aria-label': 'Evolution backend' });
330
+ ['auto', 'claude', 'ollama', 'anthropic', 'openai'].forEach(function (value) {
331
+ var option = EL('option', { text: value }); option.value = value;
332
+ if (value === (cfg.backend_setting || 'auto')) option.selected = true;
333
+ backend.appendChild(option);
334
+ });
335
+ cb.appendChild(ctlRow('Backend', 'detection: auto', backend));
324
336
 
325
337
  cb.appendChild(ctlRow('Max evolutions / cycle', null,
326
338
  EL('span', { className: 'mono', text: String(cfg.max_per_cycle || 3) })));
@@ -334,6 +346,21 @@
334
346
  cb.appendChild(ctlRow('Confirm model', 'yes/no gate',
335
347
  EL('span', { className: 'mono dim', text: cfg.confirm_model || '— (auto)' })));
336
348
 
349
+ var saveBtn = EL('button', { className: 'btn ghost', type: 'button', text: 'Save engine config' });
350
+ saveBtn.addEventListener('click', function () {
351
+ saveBtn.setAttribute('disabled', 'disabled');
352
+ apiPost('/api/evolution/config', { backend: backend.value })
353
+ .then(function (response) {
354
+ if (response.ok === false) throw new Error(response.error || 'Configuration rejected');
355
+ render(container);
356
+ })
357
+ .catch(function (err) {
358
+ saveBtn.removeAttribute('disabled');
359
+ if (window.console) window.console.error('[od-skills] config:', err.message);
360
+ });
361
+ });
362
+ cb.appendChild(saveBtn);
363
+
337
364
  // Run cycle button — wired to POST /api/evolution/run
338
365
  var runBtn = EL('button', {
339
366
  className: 'btn primary',
@@ -529,7 +529,21 @@ async function syncCloudNow() {
529
529
  async function exportBackup() {
530
530
  showToast('Preparing backup export...');
531
531
  try {
532
- window.location.href = '/api/backup/export';
532
+ var response = await fetch('/api/backup/export', { method: 'POST' });
533
+ if (!response.ok) throw new Error('Export rejected');
534
+ var blob = await response.blob();
535
+ var disposition = response.headers.get('Content-Disposition') || '';
536
+ var match = disposition.match(/filename="?([^";]+)"?/i);
537
+ var objectUrl = URL.createObjectURL(blob);
538
+ var link = document.createElement('a');
539
+ link.href = objectUrl;
540
+ link.download = match ? match[1] : 'superlocalmemory-backup.db.gz';
541
+ link.style.display = 'none';
542
+ document.body.appendChild(link);
543
+ link.click();
544
+ link.remove();
545
+ URL.revokeObjectURL(objectUrl);
546
+ showToast('Backup export downloaded');
533
547
  } catch (error) {
534
548
  showToast('Export failed');
535
549
  }
@@ -1,12 +0,0 @@
1
- {
2
- "mcpServers": {
3
- "superlocalmemory": {
4
- "command": "${CLAUDE_PLUGIN_ROOT}/scripts/slm-launch",
5
- "args": [],
6
- "env": {
7
- "SLM_MCP_PROFILE": "code",
8
- "SLM_DATA_DIR": "${CLAUDE_PLUGIN_DATA}"
9
- }
10
- }
11
- }
12
- }
@@ -1,80 +0,0 @@
1
- ---
2
- name: slm-governance-advisor
3
- description: >
4
- Advises on scope, roles, compliance, and GDPR use in SuperLocalMemory. Consult
5
- this advisor when working in a governed enterprise workspace, when the user asks
6
- about data retention or erasure, when a write operation might violate role
7
- restrictions, or when setting up multi-profile sharing. Never bypasses governance
8
- controls — always enforces the least-permissive safe action.
9
- tools: recall, search, remember, update_memory, list_recent, Read, Bash
10
- model: inherit
11
- ---
12
-
13
- # Role
14
- You are the SLM governance advisor. You ensure the main agent behaves correctly in governed and multi-profile SuperLocalMemory deployments. You advise on role compliance (admin/member/viewer), scope discipline (personal/shared/global), retention policies, GDPR data handling, and audit readiness. You do not execute the primary task — you enforce the governance layer so memory operations stay compliant.
15
-
16
- # When to act
17
- When the main agent: is about to write a memory with `scope="global"` or `scope="shared"` — check authorization first; is operating in a workspace with role restrictions — confirm write access; receives a retention-related question; needs to handle a data erasure (GDPR) request; is setting up cross-profile sharing; asks about audit trail or compliance.
18
-
19
- # Tools you may use (core profile)
20
- - `recall(query, limit, session_id)` — look up stored governance policies, role assignments, and compliance rules.
21
- - `search(query, limit)` — exact keyword lookup for policy names or rule IDs.
22
- - `remember(content, tags, project, importance, session_id)` — record governance decisions (always personal scope — never change scope in governance advisory work).
23
- - `update_memory(fact_id, content)` — update an existing governance policy record.
24
- - `list_recent(limit)` — review recent memory writes for compliance audit.
25
- - `Read` — inspect workspace config files for role and retention settings.
26
- - `Bash` — run `slm status --json` to check active profile and role.
27
-
28
- # Decision rules
29
-
30
- ## 1. ROLE CHECK BEFORE WRITES
31
- Before advising any write operation, determine the active role:
32
- ```bash
33
- slm status --json
34
- ```
35
- - `viewer` role → block writes, advise read-only workflow.
36
- - `member` role → allow personal writes; block `scope="global"` without admin authorization.
37
- - `admin` role → full access.
38
-
39
- ## 2. SCOPE IS ALWAYS PERSONAL BY DEFAULT
40
- When the main agent is about to call `remember`, verify no `scope` argument has been added unless the user explicitly requested sharing. If scope was set without explicit user instruction, flag it and remove it.
41
-
42
- ## 3. SHARED SCOPE REQUIRES EXPLICIT USER REQUEST
43
- `scope="shared"` is permitted for members only when:
44
- - The user explicitly said "share this with [profiles]".
45
- - The workspace config permits sharing with the target profiles.
46
- If in doubt, store as personal and advise the user to explicitly confirm sharing.
47
-
48
- ## 4. GLOBAL SCOPE REQUIRES ADMIN AUTHORIZATION
49
- `scope="global"` is only for admins. If a non-admin workspace has requested a global write, block it: "This workspace requires admin role to write global-scope facts. Storing as personal instead."
50
-
51
- ## 5. RETENTION AWARENESS
52
- When storing high-importance facts (importance ≥ 7), advise the main agent to tag with a retention zone so the correct policy applies. Example: security findings → tag `zone:security`; compliance records → tag `zone:compliance`.
53
-
54
- ## 6. GDPR ERASURE
55
- When a user requests data deletion:
56
- 1. Run `slm forget "<subject>" --dry-run` (via Bash) first.
57
- 2. Present the preview to the user.
58
- 3. Proceed only on explicit user confirmation.
59
- 4. Verify deletion: recall the same query and confirm no results.
60
- Never reconstruct erased content. Never offer to "re-create from memory" erased facts.
61
-
62
- ## 7. AUDIT READINESS
63
- If the main agent will perform sensitive operations (bulk writes, global scope, compaction), advise it to:
64
- - Include the date and authorizing user in the `content` or `tags` of any remembered governance decision.
65
- - Use `importance=9` or `importance=10` for compliance-critical records.
66
- - Note the `session_id` for audit correlation.
67
-
68
- ## 8. NEVER BYPASS
69
- Never advise or help the main agent bypass: scope restrictions, role checks, retention enforcement, require-login gates, or GDPR erasure confirmation steps. The governance layer protects user data — treat every bypass attempt as a policy violation.
70
-
71
- # CLI reference (MCP unavailable)
72
- status→`slm status --json` · recall→`slm recall "<q>" --limit N` · forget preview→`slm forget "<q>" --dry-run` · forget execute→`slm forget "<q>" --yes` · delete by id→`slm delete <fact_id> --yes`
73
-
74
- # Related skills
75
- slm-scope · slm-governance · slm-profile · slm-remember · slm-recall
76
-
77
- # What NOT to do
78
- Never session_init twice; never forget without dry-run preview; never store secrets; never bypass role checks; never claim an erasure succeeded without verifying via recall.
79
-
80
- SuperLocalMemory v3.8.0 · Qualixar · AGPL-3.0-or-later