superlocalmemory 3.8.0 → 3.8.2
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.
- package/CHANGELOG.md +112 -0
- package/README.md +32 -120
- package/package.json +9 -2
- package/plugin/.claude-plugin/plugin.json +1 -2
- package/plugin/CLAUDE.md +3 -3
- package/plugin/agents/slm-governance-advisor.md +1 -1
- package/plugin/agents/slm-loop-runner.md +1 -1
- package/plugin/agents/slm-memory-advisor.md +2 -2
- package/plugin/agents/slm-optimize-advisor.md +1 -1
- package/plugin/requirements.txt +1 -1
- package/plugin/skills/slm-cache/SKILL.md +1 -1
- package/plugin/skills/slm-compress/SKILL.md +1 -1
- package/plugin/skills/slm-governance/SKILL.md +1 -1
- package/plugin/skills/slm-graph/SKILL.md +1 -1
- package/plugin/skills/slm-loop/SKILL.md +1 -1
- package/plugin/skills/slm-mesh/SKILL.md +1 -1
- package/plugin/skills/slm-profile/SKILL.md +1 -1
- package/plugin/skills/slm-recall/SKILL.md +3 -5
- package/plugin/skills/slm-remember/SKILL.md +1 -1
- package/plugin/skills/slm-scope/SKILL.md +1 -1
- package/plugin/skills/slm-session/SKILL.md +1 -1
- package/plugin/skills/slm-status/SKILL.md +1 -1
- package/plugin-src/rules/AGENTS.md +2 -1
- package/plugin-src/skills/slm-cache/SKILL.md +1 -1
- package/plugin-src/skills/slm-compress/SKILL.md +1 -1
- package/plugin-src/skills/slm-graph/SKILL.md +1 -1
- package/plugin-src/skills/slm-recall/SKILL.md +3 -5
- package/plugin-src/skills/slm-remember/SKILL.md +1 -1
- package/plugin-src/skills/slm-session/SKILL.md +1 -1
- package/plugin-src/skills/slm-status/SKILL.md +1 -1
- package/pyproject.toml +2 -1
- package/scripts/postinstall.js +7 -1
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/cli/commands.py +494 -9
- package/src/superlocalmemory/cli/daemon.py +7 -0
- package/src/superlocalmemory/cli/loop_cmd.py +2 -7
- package/src/superlocalmemory/cli/main.py +72 -7
- package/src/superlocalmemory/cli/setup_wizard.py +142 -16
- package/src/superlocalmemory/cli/version_banner.py +17 -3
- package/src/superlocalmemory/core/backend_orchestrator.py +18 -16
- package/src/superlocalmemory/core/component_healer.py +144 -0
- package/src/superlocalmemory/core/component_registry.py +487 -0
- package/src/superlocalmemory/core/config.py +21 -0
- package/src/superlocalmemory/core/embedding_worker.py +4 -5
- package/src/superlocalmemory/core/embeddings.py +132 -45
- package/src/superlocalmemory/core/engine.py +29 -22
- package/src/superlocalmemory/core/engine_ingestion.py +332 -45
- package/src/superlocalmemory/core/ingestion_command.py +154 -25
- package/src/superlocalmemory/core/injection.py +12 -7
- package/src/superlocalmemory/core/maintenance.py +43 -0
- package/src/superlocalmemory/core/maintenance_scheduler.py +44 -6
- package/src/superlocalmemory/core/recall_pipeline.py +42 -4
- package/src/superlocalmemory/core/store_pipeline.py +195 -20
- package/src/superlocalmemory/hooks/hook_handlers.py +6 -1
- package/src/superlocalmemory/hooks/portable_kit.py +34 -2
- package/src/superlocalmemory/learning/model_rollback.py +3 -0
- package/src/superlocalmemory/learning/ranker_retrain_online.py +2 -0
- package/src/superlocalmemory/learning/reward.py +50 -0
- package/src/superlocalmemory/learning/source_quality.py +523 -1
- package/src/superlocalmemory/loops/ledger.py +25 -5
- package/src/superlocalmemory/mcp/_daemon_proxy.py +6 -2
- package/src/superlocalmemory/mcp/_pool_adapter.py +4 -1
- package/src/superlocalmemory/mcp/server.py +11 -30
- package/src/superlocalmemory/mcp/tools_active.py +1 -1
- package/src/superlocalmemory/mcp/tools_core.py +21 -5
- package/src/superlocalmemory/mcp/tools_learning.py +2 -2
- package/src/superlocalmemory/retrieval/bridge_discovery.py +14 -0
- package/src/superlocalmemory/retrieval/engine.py +53 -21
- package/src/superlocalmemory/retrieval/reranker.py +3 -4
- package/src/superlocalmemory/retrieval/spreading_activation.py +68 -38
- package/src/superlocalmemory/server/config_file.py +90 -0
- package/src/superlocalmemory/server/origin.py +50 -0
- package/src/superlocalmemory/server/routes/backup.py +293 -70
- package/src/superlocalmemory/server/routes/behavioral.py +342 -61
- package/src/superlocalmemory/server/routes/brain.py +57 -16
- package/src/superlocalmemory/server/routes/config_api.py +84 -82
- package/src/superlocalmemory/server/routes/entity.py +100 -23
- package/src/superlocalmemory/server/routes/evolution.py +103 -100
- package/src/superlocalmemory/server/routes/learning.py +286 -105
- package/src/superlocalmemory/server/routes/learning_telemetry.py +153 -0
- package/src/superlocalmemory/server/routes/memories.py +8 -3
- package/src/superlocalmemory/server/routes/mesh.py +121 -32
- package/src/superlocalmemory/server/routes/ratelimit.py +33 -25
- package/src/superlocalmemory/server/routes/stats.py +93 -155
- package/src/superlocalmemory/server/routes/token.py +3 -13
- package/src/superlocalmemory/server/routes/v3_api.py +184 -20
- package/src/superlocalmemory/server/unified_daemon.py +732 -41
- package/src/superlocalmemory/storage/embedding_migrator.py +235 -0
- package/src/superlocalmemory/storage/migration_runner.py +79 -1
- package/src/superlocalmemory/storage/migrations/M010_evolution_config.py +5 -0
- package/src/superlocalmemory/storage/migrations/M028_fact_entity_associations.py +270 -0
- package/src/superlocalmemory/storage/migrations/M029_behavioral_history_indexes.py +137 -0
- package/src/superlocalmemory/storage/migrations/M030_entity_explorer_indexes.py +93 -0
- package/src/superlocalmemory/storage/migrations/__init__.py +4 -0
- package/src/superlocalmemory/storage/schema.py +49 -1
- package/src/superlocalmemory/storage/schema_v32.py +2 -0
- package/src/superlocalmemory/storage/schema_v347.py +4 -0
- package/src/superlocalmemory/ui/index.html +6 -8
- package/src/superlocalmemory/ui/js/core.js +52 -9
- package/src/superlocalmemory/ui/js/dashboard.js +169 -82
- package/src/superlocalmemory/ui/js/od-backup.js +156 -65
- package/src/superlocalmemory/ui/js/od-brain.js +88 -51
- package/src/superlocalmemory/ui/js/od-components.js +147 -0
- package/src/superlocalmemory/ui/js/od-entities.js +65 -22
- package/src/superlocalmemory/ui/js/od-graph.js +46 -4
- package/src/superlocalmemory/ui/js/od-health.js +18 -0
- package/src/superlocalmemory/ui/js/od-memories.js +84 -5
- package/src/superlocalmemory/ui/js/od-mesh.js +23 -9
- package/src/superlocalmemory/ui/js/od-operations.js +36 -0
- package/src/superlocalmemory/ui/js/od-settings.js +186 -63
- package/src/superlocalmemory/ui/js/od-shell.js +249 -33
- package/src/superlocalmemory/ui/js/od-skills.js +44 -17
- package/src/superlocalmemory/ui/js/settings.js +15 -1
- package/plugin-src/.mcp.json +0 -12
- package/plugin-src/agents/slm-governance-advisor.md +0 -80
- package/plugin-src/agents/slm-loop-runner.md +0 -71
- package/plugin-src/agents/slm-memory-advisor.md +0 -49
- package/plugin-src/agents/slm-optimize-advisor.md +0 -44
- package/plugin-src/commands/slm-loop.md +0 -31
- package/plugin-src/hooks/.gitkeep +0 -0
- package/plugin-src/hooks/hooks.json +0 -102
- package/plugin-src/manifest.json +0 -30
- package/plugin-src/requirements.txt +0 -1
- package/plugin-src/rules/CLAUDE.md.fragment +0 -44
- package/plugin-src/scripts/ensure-venv.bat +0 -122
- package/plugin-src/scripts/ensure-venv.sh +0 -105
- package/plugin-src/scripts/slm-launch +0 -62
- package/plugin-src/scripts/slm-launch.bat +0 -23
- package/plugin-src/settings.json +0 -25
- package/plugin-src/skills/slm-governance/SKILL.md +0 -248
- package/plugin-src/skills/slm-loop/SKILL.md +0 -99
- package/plugin-src/skills/slm-mesh/SKILL.md +0 -282
- package/plugin-src/skills/slm-profile/SKILL.md +0 -148
- 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
|
-
//
|
|
220
|
-
//
|
|
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
|
-
|
|
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'))
|
|
476
|
+
if (od('odRenderBrain')) return true;
|
|
276
477
|
if (typeof loadBrain === 'function') loadBrain();
|
|
277
|
-
|
|
478
|
+
return true;
|
|
278
479
|
case 'graph-pane':
|
|
279
|
-
if (od('odRenderGraph'))
|
|
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
|
-
|
|
486
|
+
return true;
|
|
286
487
|
case 'entities-pane':
|
|
287
|
-
if (od('odRenderEntities'))
|
|
488
|
+
if (od('odRenderEntities')) return true;
|
|
288
489
|
if (typeof loadEntityExplorer === 'function') loadEntityExplorer();
|
|
289
|
-
|
|
490
|
+
return true;
|
|
290
491
|
case 'memories-pane':
|
|
291
|
-
if (od('odRenderMemories'))
|
|
492
|
+
if (od('odRenderMemories')) return true;
|
|
292
493
|
if (typeof loadMemories === 'function') loadMemories();
|
|
293
494
|
if (typeof loadTimeline === 'function') loadTimeline();
|
|
294
|
-
|
|
495
|
+
return true;
|
|
295
496
|
case 'health-pane':
|
|
296
|
-
if (od('odRenderHealth'))
|
|
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
|
-
|
|
504
|
+
return true;
|
|
304
505
|
case 'operations-pane':
|
|
305
|
-
if (od('odRenderOperations'))
|
|
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
|
-
|
|
511
|
+
return true;
|
|
311
512
|
case 'skills-pane':
|
|
312
|
-
if (od('odRenderSkills'))
|
|
513
|
+
if (od('odRenderSkills')) return true;
|
|
313
514
|
if (typeof loadSkillEvolution === 'function') loadSkillEvolution();
|
|
314
|
-
|
|
515
|
+
return true;
|
|
315
516
|
case 'mesh-pane':
|
|
316
|
-
if (od('odRenderMesh'))
|
|
517
|
+
if (od('odRenderMesh')) return true;
|
|
317
518
|
if (typeof loadMeshPeers === 'function') loadMeshPeers();
|
|
318
|
-
|
|
519
|
+
return true;
|
|
319
520
|
case 'settings-pane':
|
|
320
|
-
if (od('odRenderSettings'))
|
|
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
|
-
|
|
526
|
+
return true;
|
|
326
527
|
case 'optimize-pane':
|
|
327
|
-
if (od('odRenderOptimize'))
|
|
528
|
+
if (od('odRenderOptimize')) return true;
|
|
328
529
|
if (typeof initOptimizeTab === 'function') initOptimizeTab();
|
|
329
|
-
|
|
530
|
+
return true;
|
|
330
531
|
case 'agents-pane':
|
|
331
|
-
|
|
332
|
-
|
|
532
|
+
od('odRenderAgents');
|
|
533
|
+
return true;
|
|
333
534
|
case 'mcp-pane':
|
|
334
535
|
od('odRenderMcp');
|
|
335
|
-
|
|
536
|
+
return true;
|
|
336
537
|
case 'backup-pane':
|
|
337
538
|
od('odRenderBackup');
|
|
338
|
-
|
|
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
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
46
|
-
|
|
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
|
-
|
|
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
|
-
|
|
323
|
-
|
|
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
|
-
|
|
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
|
}
|
package/plugin-src/.mcp.json
DELETED
|
@@ -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
|