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
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
// Renders the approved design into window.odRenderBrain(container).
|
|
5
5
|
// ALL data from these confirmed live endpoints (no seed / no invented data):
|
|
6
6
|
// GET /api/learning/status → ranking_phase, stats, engagement, tech_preferences,
|
|
7
|
-
// workflow_patterns,
|
|
8
|
-
// GET /api/behavioral/status →
|
|
9
|
-
//
|
|
7
|
+
// workflow_patterns, persisted source-quality posteriors
|
|
8
|
+
// GET /api/behavioral/status → reward_telemetry, explicit outcome counts,
|
|
9
|
+
// patterns[], transfers, recent_outcomes[]
|
|
10
10
|
// GET /api/behavioral/assertions?category=skill_performance&limit=50 → assertions[]
|
|
11
11
|
// GET /api/behavioral/tool-events?limit=500 → events[].{created_at,tool_name,…}
|
|
12
12
|
//
|
|
@@ -189,11 +189,21 @@
|
|
|
189
189
|
var stats = (learning && learning.stats) || {};
|
|
190
190
|
var eng = (learning && learning.engagement) || {};
|
|
191
191
|
var beh = behavioral || {};
|
|
192
|
-
var
|
|
193
|
-
var
|
|
194
|
-
var
|
|
195
|
-
var
|
|
196
|
-
var
|
|
192
|
+
var ranker = (learning && learning.ranker_phase) || {};
|
|
193
|
+
var gates = ranker.gates || {};
|
|
194
|
+
var ruleGate = Math.max(1, Number(gates.rule_based_min_signals || 1));
|
|
195
|
+
var mlGate = Math.max(ruleGate, Number(gates.ml_model_min_signals || ruleGate));
|
|
196
|
+
var signals = Number(ranker.signals != null
|
|
197
|
+
? ranker.signals : stats.ranker_signal_count || 0);
|
|
198
|
+
var pct = Math.min(100, Math.round(signals / mlGate * 100));
|
|
199
|
+
var phase = ranker.key || (learning && learning.ranking_phase) || 'baseline';
|
|
200
|
+
var phaseNumber = Number(ranker.phase || 1);
|
|
201
|
+
var modelActive = Boolean(ranker.model_active);
|
|
202
|
+
var phaseDelta = modelActive
|
|
203
|
+
? 'Verified active model'
|
|
204
|
+
: signals < mlGate
|
|
205
|
+
? fmtNum(mlGate - signals) + ' to ML data gate'
|
|
206
|
+
: 'ML data gate met · verified model required';
|
|
197
207
|
var healthStatus = (eng.health_status || 'INACTIVE').toUpperCase();
|
|
198
208
|
var healthColor = healthStatus === 'HEALTHY' ? 'var(--ok)'
|
|
199
209
|
: healthStatus === 'ACTIVE' ? 'var(--cyan)' : undefined;
|
|
@@ -203,7 +213,7 @@
|
|
|
203
213
|
var strip = EL('div', { className: 'kpi-strip', style: 'margin-bottom:16px' });
|
|
204
214
|
// Ranking phase: text label → isNumeric=false (font-size:24px to match design)
|
|
205
215
|
strip.appendChild(kpiCard('skill', 'Ranking phase', phaseLabel(phase),
|
|
206
|
-
|
|
216
|
+
phaseDelta, modelActive, undefined, false));
|
|
207
217
|
// Feedback signals: numeric → isNumeric=true
|
|
208
218
|
strip.appendChild(kpiCard('optimize', 'Feedback signals', fmtNum(signals),
|
|
209
219
|
'▲ ' + fmtNum(stats.unique_queries || 0) + ' unique queries', true, undefined, true));
|
|
@@ -230,7 +240,7 @@
|
|
|
230
240
|
var pmeta = EL('div', {
|
|
231
241
|
style: 'display:flex;justify-content:space-between;font-size:12px;color:var(--fg-2);margin-bottom:8px',
|
|
232
242
|
});
|
|
233
|
-
pmeta.appendChild(EL('span', { text: fmtNum(signals) + ' / ' + fmtNum(
|
|
243
|
+
pmeta.appendChild(EL('span', { text: fmtNum(signals) + ' / ' + fmtNum(mlGate) + ' signals' }));
|
|
234
244
|
pmeta.appendChild(EL('span', { className: 'num', text: pct + '%' }));
|
|
235
245
|
pb.appendChild(pmeta);
|
|
236
246
|
pb.appendChild(meter(pct));
|
|
@@ -238,9 +248,24 @@
|
|
|
238
248
|
style: 'display:flex;justify-content:space-between;margin-top:18px;gap:12px',
|
|
239
249
|
});
|
|
240
250
|
[
|
|
241
|
-
{
|
|
242
|
-
|
|
243
|
-
|
|
251
|
+
{
|
|
252
|
+
t: 'Baseline',
|
|
253
|
+
d: '0–' + Math.max(0, ruleGate - 1) + ' signals',
|
|
254
|
+
done: phaseNumber > 1,
|
|
255
|
+
now: phaseNumber === 1,
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
t: 'Rule-Based',
|
|
259
|
+
d: fmtNum(ruleGate) + '–' + fmtNum(Math.max(ruleGate, mlGate - 1)) + ' signals',
|
|
260
|
+
done: phaseNumber > 2,
|
|
261
|
+
now: phaseNumber === 2,
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
t: 'ML Model',
|
|
265
|
+
d: fmtNum(mlGate) + '+ · ' + (modelActive ? 'verified active' : 'verified model required'),
|
|
266
|
+
done: false,
|
|
267
|
+
now: phaseNumber === 3 && modelActive,
|
|
268
|
+
},
|
|
244
269
|
].forEach(function (phI) {
|
|
245
270
|
var el = EL('div', {
|
|
246
271
|
className: 'phase' + (phI.done ? ' done' : '') + (phI.now ? ' now' : ''),
|
|
@@ -264,6 +289,7 @@
|
|
|
264
289
|
['Learning DB size', fmtKB(stats.db_size_kb)],
|
|
265
290
|
['Patterns learned', String(pCount)],
|
|
266
291
|
['Models trained', String(stats.models_trained || 0)],
|
|
292
|
+
['Verified active models', String(stats.models_active_verified || 0)],
|
|
267
293
|
['Sources tracked', String(stats.tracked_sources || 0)],
|
|
268
294
|
].forEach(function (row) {
|
|
269
295
|
var r = EL('div', { className: 'list-row' });
|
|
@@ -282,11 +308,11 @@
|
|
|
282
308
|
grid.appendChild(priv);
|
|
283
309
|
sec.appendChild(grid);
|
|
284
310
|
|
|
285
|
-
//
|
|
311
|
+
// Activity heatmap (tool events are activity, never reward labels)
|
|
286
312
|
var hmc = EL('div', { className: 'card', style: 'margin-top:16px' });
|
|
287
313
|
var hmh = EL('div', { className: 'card-head' });
|
|
288
|
-
hmh.appendChild(EL('h3', { text:
|
|
289
|
-
hmh.appendChild(EL('span', { className: 'sub', text: '
|
|
314
|
+
hmh.appendChild(EL('h3', { text: 'Memory activity' }));
|
|
315
|
+
hmh.appendChild(EL('span', { className: 'sub', text: 'tool events · last 26 weeks' }));
|
|
290
316
|
hmh.appendChild(EL('div', { className: 'spacer' }));
|
|
291
317
|
hmh.appendChild(heatLegend());
|
|
292
318
|
hmc.appendChild(hmh);
|
|
@@ -302,21 +328,30 @@
|
|
|
302
328
|
// ======================================================================
|
|
303
329
|
// Tab: REWARD SIGNAL
|
|
304
330
|
// ======================================================================
|
|
305
|
-
function buildReward(behavioral
|
|
331
|
+
function buildReward(behavioral) {
|
|
306
332
|
var sec = EL('section', { className: 'tabpane', 'data-p': 'reward' });
|
|
307
333
|
var beh = behavioral || {};
|
|
334
|
+
var reward = beh.reward_telemetry || {};
|
|
335
|
+
var timeline = reward.timeline || [];
|
|
336
|
+
var rewardDateMap = {};
|
|
337
|
+
timeline.forEach(function (point) {
|
|
338
|
+
if (point && point.date) rewardDateMap[String(point.date)] = Number(point.count || 0);
|
|
339
|
+
});
|
|
308
340
|
|
|
309
341
|
// Density heatmap
|
|
310
342
|
var hmcr = EL('div', { className: 'card', style: 'margin-bottom:16px' });
|
|
311
343
|
var hmhr = EL('div', { className: 'card-head' });
|
|
312
344
|
hmhr.appendChild(EL('h3', { text: 'Reward signal density' }));
|
|
313
|
-
hmhr.appendChild(EL('span', {
|
|
345
|
+
hmhr.appendChild(EL('span', {
|
|
346
|
+
className: 'sub',
|
|
347
|
+
text: 'settled numeric labels per day · last ' + Number(reward.window_days || 182) + ' days',
|
|
348
|
+
}));
|
|
314
349
|
hmhr.appendChild(EL('div', { className: 'spacer' }));
|
|
315
350
|
hmhr.appendChild(heatLegend());
|
|
316
351
|
hmcr.appendChild(hmhr);
|
|
317
352
|
var hmbr = EL('div', { className: 'card-pad', style: 'overflow-x:auto' });
|
|
318
353
|
var hmElR = EL('div', { className: 'heatmap', id: 'od-brain-heat2' });
|
|
319
|
-
buildHeatmap(hmElR,
|
|
354
|
+
buildHeatmap(hmElR, rewardDateMap, 26);
|
|
320
355
|
hmbr.appendChild(hmElR);
|
|
321
356
|
hmcr.appendChild(hmbr);
|
|
322
357
|
sec.appendChild(hmcr);
|
|
@@ -324,21 +359,28 @@
|
|
|
324
359
|
// 2-column: sparkline + outcome mix
|
|
325
360
|
var grid = EL('div', { className: 'grid', style: 'grid-template-columns:1fr 1fr;align-items:start' });
|
|
326
361
|
|
|
327
|
-
//
|
|
362
|
+
// Average settled reward and real daily series
|
|
328
363
|
var fbCard = EL('div', { className: 'card' });
|
|
329
364
|
var fbH = EL('div', { className: 'card-head' });
|
|
330
|
-
fbH.appendChild(EL('h3', { text: '
|
|
331
|
-
fbH.appendChild(EL('span', {
|
|
365
|
+
fbH.appendChild(EL('h3', { text: 'Average settled reward' }));
|
|
366
|
+
fbH.appendChild(EL('span', {
|
|
367
|
+
className: 'sub',
|
|
368
|
+
text: fmtNum(reward.count || 0) + ' finalized labels',
|
|
369
|
+
}));
|
|
332
370
|
fbCard.appendChild(fbH);
|
|
333
371
|
var fbB = EL('div', { className: 'card-pad' });
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
372
|
+
if (reward.average != null) {
|
|
373
|
+
fbB.appendChild(EL('div', {
|
|
374
|
+
className: 'value num',
|
|
375
|
+
style: 'font-size:30px;margin-bottom:12px',
|
|
376
|
+
text: Number(reward.average).toFixed(3),
|
|
377
|
+
}));
|
|
340
378
|
}
|
|
341
|
-
var
|
|
379
|
+
var fbSp = EL('div', { id: 'od-brain-sp-fb' });
|
|
380
|
+
var sparkVals = timeline.slice(-30).map(function (point) {
|
|
381
|
+
return Number(point.average || 0);
|
|
382
|
+
});
|
|
383
|
+
var hasSparkData = sparkVals.length > 0;
|
|
342
384
|
if (hasSparkData && typeof window.slmSpark === 'function') {
|
|
343
385
|
fbSp.innerHTML = window.slmSpark(sparkVals, { w: 600, h: 150, color: 'var(--violet)' });
|
|
344
386
|
var sv1 = fbSp.querySelector('svg'); if (sv1) sv1.style.height = '150px';
|
|
@@ -346,33 +388,33 @@
|
|
|
346
388
|
fbSp.appendChild(EL('p', {
|
|
347
389
|
className: 'muted',
|
|
348
390
|
style: 'padding:32px;text-align:center;font-size:13px',
|
|
349
|
-
text: 'No
|
|
391
|
+
text: 'No settled reward history is available yet.',
|
|
350
392
|
}));
|
|
351
393
|
}
|
|
352
394
|
fbB.appendChild(fbSp);
|
|
353
395
|
fbCard.appendChild(fbB);
|
|
354
396
|
grid.appendChild(fbCard);
|
|
355
397
|
|
|
356
|
-
//
|
|
398
|
+
// Settled reward distribution
|
|
357
399
|
var outCard = EL('div', { className: 'card' });
|
|
358
400
|
var outH = EL('div', { className: 'card-head' });
|
|
359
|
-
outH.appendChild(EL('h3', { text: '
|
|
360
|
-
outH.appendChild(EL('span', { className: 'sub', text: '
|
|
401
|
+
outH.appendChild(EL('h3', { text: 'Reward distribution' }));
|
|
402
|
+
outH.appendChild(EL('span', { className: 'sub', text: 'engagement-derived settled labels' }));
|
|
361
403
|
outCard.appendChild(outH);
|
|
362
404
|
var outB = EL('div', { className: 'card-pad', id: 'od-brain-outcomes' });
|
|
363
|
-
var total =
|
|
364
|
-
var bd =
|
|
405
|
+
var total = Number(reward.count || 0);
|
|
406
|
+
var bd = reward.distribution || {};
|
|
365
407
|
if (total === 0) {
|
|
366
408
|
outB.appendChild(EL('p', {
|
|
367
409
|
className: 'muted',
|
|
368
410
|
style: 'padding:16px;text-align:center;font-size:13px',
|
|
369
|
-
text: 'No
|
|
411
|
+
text: 'No settled reward labels yet. Recall engagement will populate this view.',
|
|
370
412
|
}));
|
|
371
413
|
} else {
|
|
372
414
|
[
|
|
373
|
-
['
|
|
374
|
-
['
|
|
375
|
-
['
|
|
415
|
+
['Positive (> 0.6)', bd.positive || 0, 'var(--ok)'],
|
|
416
|
+
['Neutral (0.4–0.6)', bd.neutral || 0, 'var(--cyan)'],
|
|
417
|
+
['Negative (< 0.4)', bd.negative || 0, 'var(--danger)'],
|
|
376
418
|
].forEach(function (r) {
|
|
377
419
|
var p2 = Math.round(r[1] / total * 100);
|
|
378
420
|
var rw = EL('div', { style: 'margin-bottom:13px' });
|
|
@@ -454,7 +496,7 @@
|
|
|
454
496
|
wcb.appendChild(EL('p', {
|
|
455
497
|
className: 'muted',
|
|
456
498
|
style: 'padding:16px;text-align:center;font-size:13px',
|
|
457
|
-
text: '
|
|
499
|
+
text: 'No sequence or temporal patterns recorded yet. Interest signals are not shown as workflows.',
|
|
458
500
|
}));
|
|
459
501
|
} else {
|
|
460
502
|
wfPats.slice(0, 3).forEach(function (wf) {
|
|
@@ -482,9 +524,7 @@
|
|
|
482
524
|
xch.appendChild(EL('span', { className: 'sub', text: 'patterns reused across projects' }));
|
|
483
525
|
xc.appendChild(xch);
|
|
484
526
|
var xcb = EL('div', { className: 'card-pad' });
|
|
485
|
-
var xPats =
|
|
486
|
-
return p.pattern_type === 'cross_project' || p.is_transferable;
|
|
487
|
-
});
|
|
527
|
+
var xPats = beh.cross_project_patterns || [];
|
|
488
528
|
if (xPats.length === 0) {
|
|
489
529
|
xcb.appendChild(EL('p', {
|
|
490
530
|
className: 'muted',
|
|
@@ -535,9 +575,9 @@
|
|
|
535
575
|
item.appendChild(bdg);
|
|
536
576
|
item.appendChild(EL('span', {
|
|
537
577
|
style: 'flex:1;font-size:13px',
|
|
538
|
-
text: String(r.
|
|
578
|
+
text: String(r.action_type || 'other'),
|
|
539
579
|
}));
|
|
540
|
-
item.appendChild(EL('time', { text: String(r.
|
|
580
|
+
item.appendChild(EL('time', { text: String(r.timestamp || '') }));
|
|
541
581
|
rcb.appendChild(item);
|
|
542
582
|
});
|
|
543
583
|
}
|
|
@@ -614,17 +654,14 @@
|
|
|
614
654
|
var card = EL('div', { className: 'card' });
|
|
615
655
|
var ch = EL('div', { className: 'card-head' });
|
|
616
656
|
ch.appendChild(EL('h3', { text: 'Source quality' }));
|
|
617
|
-
ch.appendChild(EL('span', { className: 'sub', text: '
|
|
657
|
+
ch.appendChild(EL('span', { className: 'sub', text: 'persisted source-outcome posterior · 0.0–1.0' }));
|
|
618
658
|
card.appendChild(ch);
|
|
619
659
|
var cb = EL('div', { className: 'card-pad' });
|
|
620
660
|
if (entries.length === 0) {
|
|
621
|
-
// TODO: GET /api/learning/status → source_scores is empty until ML ranking pipeline
|
|
622
|
-
// processes sufficient interactions and runs consolidation.
|
|
623
661
|
cb.appendChild(EL('p', {
|
|
624
662
|
className: 'muted',
|
|
625
663
|
style: 'padding:16px;text-align:center;font-size:13px',
|
|
626
|
-
text: '
|
|
627
|
-
'No data yet — scores appear after consolidation runs.',
|
|
664
|
+
text: 'No source-quality observations yet. Recall hits alone do not establish source quality.',
|
|
628
665
|
}));
|
|
629
666
|
} else {
|
|
630
667
|
entries.forEach(function (k) {
|
|
@@ -742,7 +779,7 @@
|
|
|
742
779
|
head,
|
|
743
780
|
buildTabRow(pCount),
|
|
744
781
|
buildOverview(learning, behavioral, dateMap),
|
|
745
|
-
buildReward(behavioral
|
|
782
|
+
buildReward(behavioral),
|
|
746
783
|
buildBehaviour(learning, behavioral),
|
|
747
784
|
buildClients(dateMap),
|
|
748
785
|
buildSourceQuality(learning)
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// od-components.js — System Health "What's Missing" report (v3.8.2 UX-6)
|
|
2
|
+
//
|
|
3
|
+
// window.odRenderComponents(el) renders the component-registry snapshot from
|
|
4
|
+
// GET /api/v3/components → {components:[...], summary:{...}}
|
|
5
|
+
// into `el`, showing per-component status, a copy-paste fix command for the
|
|
6
|
+
// items SLM can't repair on its own, and a "Retry now" button that triggers
|
|
7
|
+
// POST /api/v3/components/heal (install token auto-attached by core.js)
|
|
8
|
+
// The daemon auto-heals on start; this panel is transparency + a manual
|
|
9
|
+
// fallback for the non-technical user.
|
|
10
|
+
//
|
|
11
|
+
// Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar — AGPL-3.0
|
|
12
|
+
|
|
13
|
+
/* global window, document, fetch, navigator, setTimeout */
|
|
14
|
+
(function () {
|
|
15
|
+
'use strict';
|
|
16
|
+
|
|
17
|
+
function esc(s) {
|
|
18
|
+
return String(s == null ? '' : s)
|
|
19
|
+
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
20
|
+
.replace(/"/g, '"').replace(/'/g, ''');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Severity/glyph/colour for one component, honouring its category so an
|
|
24
|
+
// absent optional backend is not shown as an error.
|
|
25
|
+
function view(c) {
|
|
26
|
+
var st = String(c.status || '').toLowerCase();
|
|
27
|
+
if (st === 'ok') return { g: '✓', col: 'var(--ok)', tip: 'ready' };
|
|
28
|
+
if (st === 'retrying') return { g: '⟳', col: 'var(--accent, #4a9)', tip: 'repairing' };
|
|
29
|
+
if (st === 'degraded') return { g: '⚠', col: 'var(--warn)', tip: 'degraded' };
|
|
30
|
+
// missing — severity depends on how much SLM needs it.
|
|
31
|
+
if (c.category === 'required') return { g: '✗', col: 'var(--danger)', tip: 'missing' };
|
|
32
|
+
if (c.category === 'recommended') return { g: '⚠', col: 'var(--warn)', tip: 'missing' };
|
|
33
|
+
return { g: '○', col: 'var(--fg-2)', tip: 'optional' };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function rowHtml(c) {
|
|
37
|
+
var v = view(c);
|
|
38
|
+
var fix = '';
|
|
39
|
+
if (c.status !== 'ok' && c.fix_cmd) {
|
|
40
|
+
if (c.auto_fixable) {
|
|
41
|
+
fix = '<div class="muted" style="margin-top:2px;font-size:12px">'
|
|
42
|
+
+ 'SLM repairs this automatically.</div>';
|
|
43
|
+
} else {
|
|
44
|
+
fix = '<div style="margin-top:4px;display:flex;gap:6px;align-items:center">'
|
|
45
|
+
+ '<code style="font-size:12px;background:var(--bg-2,#0002);'
|
|
46
|
+
+ 'padding:2px 6px;border-radius:4px">' + esc(c.fix_cmd) + '</code>'
|
|
47
|
+
+ '<button class="od-copy-fix" data-cmd="' + esc(c.fix_cmd) + '" '
|
|
48
|
+
+ 'style="font-size:11px;padding:1px 6px;cursor:pointer">Copy</button>'
|
|
49
|
+
+ '</div>';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return '<div style="padding:8px 0;border-bottom:1px solid var(--border,#8881)">'
|
|
53
|
+
+ '<div style="display:flex;gap:8px;align-items:baseline">'
|
|
54
|
+
+ '<span style="color:' + v.col + ';font-weight:700;width:16px;text-align:center">'
|
|
55
|
+
+ v.g + '</span>'
|
|
56
|
+
+ '<div style="flex:1">'
|
|
57
|
+
+ '<span style="font-weight:600">' + esc(c.label) + '</span>'
|
|
58
|
+
+ ' <span class="muted" style="font-size:12px">' + esc(c.detail || v.tip) + '</span>'
|
|
59
|
+
+ fix
|
|
60
|
+
+ '</div></div></div>';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function render(el, data) {
|
|
64
|
+
var comps = (data && data.components) || [];
|
|
65
|
+
var s = (data && data.summary) || {};
|
|
66
|
+
var healthy = !!s.healthy;
|
|
67
|
+
var hasAutoFix = (s.auto_fixable_missing || 0) > 0;
|
|
68
|
+
|
|
69
|
+
var head = '<div style="display:flex;justify-content:space-between;'
|
|
70
|
+
+ 'align-items:center;margin-bottom:6px">'
|
|
71
|
+
+ '<h3 style="margin:0">System Health — Components</h3>'
|
|
72
|
+
+ '<span style="padding:2px 10px;border-radius:12px;font-size:12px;'
|
|
73
|
+
+ 'background:' + (healthy ? 'var(--ok)' : 'var(--warn)') + ';color:#000">'
|
|
74
|
+
+ (healthy ? 'All good' : 'Needs attention') + '</span></div>';
|
|
75
|
+
|
|
76
|
+
var summary = '<div class="muted" style="font-size:12px;margin-bottom:8px">'
|
|
77
|
+
+ (s.ok || 0) + ' ready · ' + (s.missing || 0) + ' missing · '
|
|
78
|
+
+ (s.degraded || 0) + ' degraded'
|
|
79
|
+
+ (s.retrying ? ' · ' + s.retrying + ' repairing' : '') + '</div>';
|
|
80
|
+
|
|
81
|
+
var actions = '<div style="margin-top:10px;display:flex;gap:8px">'
|
|
82
|
+
+ '<button id="od-comp-recheck" style="padding:4px 12px;cursor:pointer">Recheck</button>'
|
|
83
|
+
+ (hasAutoFix
|
|
84
|
+
? '<button id="od-comp-retry" style="padding:4px 12px;cursor:pointer;'
|
|
85
|
+
+ 'background:var(--accent,#4a9);color:#000;border:none;border-radius:4px">'
|
|
86
|
+
+ 'Retry now</button>'
|
|
87
|
+
: '')
|
|
88
|
+
+ '</div>'
|
|
89
|
+
+ '<div class="muted" style="font-size:11px;margin-top:6px">'
|
|
90
|
+
+ 'SLM auto-repairs fixable items on start. Copy a command for anything '
|
|
91
|
+
+ 'it can’t install for you.</div>';
|
|
92
|
+
|
|
93
|
+
el.innerHTML = head + summary
|
|
94
|
+
+ '<div>' + comps.map(rowHtml).join('') + '</div>' + actions;
|
|
95
|
+
|
|
96
|
+
// Copy buttons.
|
|
97
|
+
el.querySelectorAll('.od-copy-fix').forEach(function (b) {
|
|
98
|
+
b.addEventListener('click', function () {
|
|
99
|
+
var cmd = b.getAttribute('data-cmd') || '';
|
|
100
|
+
if (navigator.clipboard) navigator.clipboard.writeText(cmd);
|
|
101
|
+
var old = b.textContent; b.textContent = 'Copied';
|
|
102
|
+
setTimeout(function () { b.textContent = old; }, 1200);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
var recheck = el.querySelector('#od-comp-recheck');
|
|
106
|
+
if (recheck) recheck.addEventListener('click', function () { load(el); });
|
|
107
|
+
var retry = el.querySelector('#od-comp-retry');
|
|
108
|
+
if (retry) retry.addEventListener('click', function () { doRetry(el, retry); });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function load(el) {
|
|
112
|
+
if (!el) return;
|
|
113
|
+
el.innerHTML = '<p class="muted" style="padding:8px 0">Checking components…</p>';
|
|
114
|
+
fetch('/api/v3/components')
|
|
115
|
+
.then(function (r) { return r.ok ? r.json() : null; })
|
|
116
|
+
.then(function (data) {
|
|
117
|
+
if (!data) {
|
|
118
|
+
el.innerHTML = '<p class="muted" style="padding:8px 0">'
|
|
119
|
+
+ 'Component health unavailable (is the daemon running?).</p>';
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
render(el, data);
|
|
123
|
+
})
|
|
124
|
+
.catch(function () {
|
|
125
|
+
el.innerHTML = '<p class="muted" style="padding:8px 0">'
|
|
126
|
+
+ 'Could not load component health.</p>';
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Trigger a background heal, then poll a few times so the panel reflects
|
|
131
|
+
// the retrying → ok transition without the user refreshing.
|
|
132
|
+
function doRetry(el, btn) {
|
|
133
|
+
btn.disabled = true; btn.textContent = 'Repairing…';
|
|
134
|
+
fetch('/api/v3/components/heal', { method: 'POST', credentials: 'same-origin' })
|
|
135
|
+
.then(function () {
|
|
136
|
+
var tries = 0;
|
|
137
|
+
(function poll() {
|
|
138
|
+
tries += 1;
|
|
139
|
+
load(el);
|
|
140
|
+
if (tries < 4) setTimeout(poll, 2500);
|
|
141
|
+
})();
|
|
142
|
+
})
|
|
143
|
+
.catch(function () { load(el); });
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
window.odRenderComponents = load;
|
|
147
|
+
})();
|
|
@@ -127,15 +127,20 @@
|
|
|
127
127
|
typeFilter: 'all',
|
|
128
128
|
search: '',
|
|
129
129
|
selected: null, // entity name
|
|
130
|
+
requestSeq: 0,
|
|
130
131
|
};
|
|
132
|
+
var _searchTimer = null;
|
|
131
133
|
|
|
132
134
|
// ── Main entry ─────────────────────────────────────────────────────────────
|
|
133
135
|
|
|
134
136
|
function render(container) {
|
|
135
137
|
if (!container) return;
|
|
138
|
+
clearTimeout(_searchTimer);
|
|
136
139
|
_injectCSS();
|
|
137
140
|
var id = 'od-ent-' + Math.random().toString(36).slice(2, 8);
|
|
138
|
-
_st = Object.assign({}, _st, {
|
|
141
|
+
_st = Object.assign({}, _st, {
|
|
142
|
+
rootId: id, page: 0, typeFilter: 'all', search: '', selected: null, requestSeq: 0,
|
|
143
|
+
});
|
|
139
144
|
container.innerHTML = _scaffold(id);
|
|
140
145
|
_wire(container, id);
|
|
141
146
|
_loadList(id);
|
|
@@ -231,6 +236,10 @@
|
|
|
231
236
|
function _loadList(id) {
|
|
232
237
|
var offset = _st.page * _st.pageSize;
|
|
233
238
|
var url = '/api/entity/list?limit=' + _st.pageSize + '&offset=' + offset;
|
|
239
|
+
if (_st.typeFilter !== 'all') url += '&type=' + encodeURIComponent(_st.typeFilter);
|
|
240
|
+
if (_st.search) url += '&search=' + encodeURIComponent(_st.search);
|
|
241
|
+
var requestSeq = _st.requestSeq + 1;
|
|
242
|
+
_st = Object.assign({}, _st, { requestSeq: requestSeq });
|
|
234
243
|
var listEl = document.getElementById(id + '-list');
|
|
235
244
|
if (listEl) {
|
|
236
245
|
listEl.innerHTML = '<div style="padding:40px;text-align:center;' +
|
|
@@ -243,6 +252,7 @@
|
|
|
243
252
|
return r.json();
|
|
244
253
|
})
|
|
245
254
|
.then(function (d) {
|
|
255
|
+
if (_st.rootId !== id || _st.requestSeq !== requestSeq) return;
|
|
246
256
|
_st = Object.assign({}, _st, {
|
|
247
257
|
entities: d.entities || [],
|
|
248
258
|
total: d.total || 0,
|
|
@@ -255,6 +265,7 @@
|
|
|
255
265
|
}
|
|
256
266
|
})
|
|
257
267
|
.catch(function (err) {
|
|
268
|
+
if (_st.rootId !== id || _st.requestSeq !== requestSeq) return;
|
|
258
269
|
var el = document.getElementById(id + '-list');
|
|
259
270
|
if (el) {
|
|
260
271
|
el.innerHTML = '<div style="padding:24px;text-align:center;' +
|
|
@@ -264,21 +275,10 @@
|
|
|
264
275
|
}
|
|
265
276
|
|
|
266
277
|
function _renderList(id) {
|
|
267
|
-
var
|
|
268
|
-
var tf = _st.typeFilter;
|
|
269
|
-
var all = _st.entities;
|
|
270
|
-
|
|
271
|
-
var filtered = all.filter(function (e) {
|
|
272
|
-
var typeOk = tf === 'all' || e.type === tf;
|
|
273
|
-
var searchOk = !q ||
|
|
274
|
-
(e.name || '').toLowerCase().indexOf(q) >= 0 ||
|
|
275
|
-
(e.type || '').toLowerCase().indexOf(q) >= 0 ||
|
|
276
|
-
(e.summary_preview || '').toLowerCase().indexOf(q) >= 0;
|
|
277
|
-
return typeOk && searchOk;
|
|
278
|
-
});
|
|
278
|
+
var filtered = _st.entities;
|
|
279
279
|
|
|
280
280
|
var cntEl = document.getElementById(id + '-count');
|
|
281
|
-
if (cntEl) cntEl.textContent =
|
|
281
|
+
if (cntEl) cntEl.textContent = _st.total.toLocaleString();
|
|
282
282
|
|
|
283
283
|
var listEl = document.getElementById(id + '-list');
|
|
284
284
|
if (!listEl) return;
|
|
@@ -286,7 +286,7 @@
|
|
|
286
286
|
if (filtered.length === 0) {
|
|
287
287
|
listEl.innerHTML = '<div style="padding:40px;text-align:center;' +
|
|
288
288
|
'color:var(--fg-2);font-size:13px">' +
|
|
289
|
-
(
|
|
289
|
+
(_st.search || _st.typeFilter !== 'all' ? 'No entities match this filter.' :
|
|
290
290
|
'No entities found. Entity extraction runs during consolidation.') +
|
|
291
291
|
'</div>';
|
|
292
292
|
return;
|
|
@@ -396,6 +396,17 @@
|
|
|
396
396
|
_dateField('Last seen', listEnt.last_seen) +
|
|
397
397
|
_dateField('Compiled', d.last_compiled_at) +
|
|
398
398
|
'</div>' +
|
|
399
|
+
// Recompile action — restored: POST /api/entity/{name}/recompile
|
|
400
|
+
// The disabled guard was removed; data-ename carries the name to _wire() handler.
|
|
401
|
+
'<div style="margin-top:16px;display:flex;align-items:center;gap:10px">' +
|
|
402
|
+
'<button data-od-act="recompile-entity" ' +
|
|
403
|
+
'data-ename="' + _esc(d.entity_name || '') + '" ' +
|
|
404
|
+
'style="padding:5px 14px;border:1px solid var(--border);border-radius:5px;' +
|
|
405
|
+
'font-size:12px;cursor:pointer;background:var(--bg-2);color:var(--fg)">' +
|
|
406
|
+
'Recompile</button>' +
|
|
407
|
+
'<span id="' + id + '-recompile-status" ' +
|
|
408
|
+
'style="font-size:12px;color:var(--fg-2)"></span>' +
|
|
409
|
+
'</div>' +
|
|
399
410
|
'</div>' +
|
|
400
411
|
'</div>' +
|
|
401
412
|
|
|
@@ -514,19 +525,48 @@
|
|
|
514
525
|
|
|
515
526
|
// ── Event delegation ──────────────────────────────────────────────────────
|
|
516
527
|
|
|
528
|
+
// Recompile entity — POST /api/entity/{name}/recompile
|
|
529
|
+
// Updates button text and status span to give feedback without a page reload.
|
|
530
|
+
function _recompileEntity(id, name, btn) {
|
|
531
|
+
if (!name) return;
|
|
532
|
+
var statusEl = document.getElementById(id + '-recompile-status');
|
|
533
|
+
btn.disabled = true;
|
|
534
|
+
btn.textContent = 'Recompiling…';
|
|
535
|
+
if (statusEl) { statusEl.textContent = ''; }
|
|
536
|
+
fetch('/api/entity/' + encodeURIComponent(name) + '/recompile', { method: 'POST' })
|
|
537
|
+
.then(function (r) { return r.json(); })
|
|
538
|
+
.then(function (data) {
|
|
539
|
+
btn.disabled = false;
|
|
540
|
+
btn.textContent = 'Recompile';
|
|
541
|
+
if (statusEl) {
|
|
542
|
+
statusEl.style.color = 'var(--accent)';
|
|
543
|
+
statusEl.textContent = data.message || 'Recompiled successfully';
|
|
544
|
+
}
|
|
545
|
+
})
|
|
546
|
+
.catch(function (err) {
|
|
547
|
+
btn.disabled = false;
|
|
548
|
+
btn.textContent = 'Recompile';
|
|
549
|
+
if (statusEl) {
|
|
550
|
+
statusEl.style.color = 'var(--danger, #e05)';
|
|
551
|
+
statusEl.textContent = 'Recompile failed: ' + (err.message || 'unknown error');
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
|
|
517
556
|
function _wire(container, id) {
|
|
518
557
|
container.addEventListener('click', function (e) {
|
|
519
558
|
var el = e.target.closest('[data-od-act]');
|
|
520
559
|
if (!el) return;
|
|
521
560
|
var act = el.dataset.odAct;
|
|
522
561
|
|
|
562
|
+
if (act === 'recompile-entity') {
|
|
563
|
+
_recompileEntity(id, el.dataset.ename, el);
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
523
566
|
if (act === 'type-filter') {
|
|
524
|
-
_st = Object.assign({}, _st, { typeFilter: el.dataset.type,
|
|
525
|
-
// Clear the search input DOM value so it stays in sync with state
|
|
526
|
-
var searchEl = document.getElementById(id + '-search');
|
|
527
|
-
if (searchEl) searchEl.value = '';
|
|
567
|
+
_st = Object.assign({}, _st, { typeFilter: el.dataset.type, page: 0 });
|
|
528
568
|
_refreshTypeChips(id, el.dataset.type);
|
|
529
|
-
|
|
569
|
+
_loadList(id);
|
|
530
570
|
return;
|
|
531
571
|
}
|
|
532
572
|
if (act === 'select-entity') {
|
|
@@ -546,8 +586,11 @@
|
|
|
546
586
|
|
|
547
587
|
container.addEventListener('input', function (e) {
|
|
548
588
|
if (e.target.dataset.odAct === 'ent-search') {
|
|
549
|
-
_st = Object.assign({}, _st, { search: e.target.value.trim() });
|
|
550
|
-
|
|
589
|
+
_st = Object.assign({}, _st, { search: e.target.value.trim(), page: 0 });
|
|
590
|
+
clearTimeout(_searchTimer);
|
|
591
|
+
_searchTimer = setTimeout(function () {
|
|
592
|
+
if (_st.rootId === id) _loadList(id);
|
|
593
|
+
}, 250);
|
|
551
594
|
}
|
|
552
595
|
});
|
|
553
596
|
}
|