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.
- package/CHANGELOG.md +60 -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 +1 -1
- 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 +1 -1
- 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 +1 -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 +1 -1
- 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/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/cli/commands.py +134 -7
- package/src/superlocalmemory/cli/daemon.py +7 -0
- package/src/superlocalmemory/cli/loop_cmd.py +2 -7
- package/src/superlocalmemory/cli/main.py +10 -4
- package/src/superlocalmemory/cli/version_banner.py +17 -3
- package/src/superlocalmemory/core/backend_orchestrator.py +18 -16
- package/src/superlocalmemory/core/embedding_worker.py +4 -5
- package/src/superlocalmemory/core/embeddings.py +119 -45
- package/src/superlocalmemory/core/engine.py +24 -21
- package/src/superlocalmemory/core/engine_ingestion.py +332 -45
- package/src/superlocalmemory/core/ingestion_command.py +134 -25
- package/src/superlocalmemory/core/injection.py +12 -7
- package/src/superlocalmemory/core/maintenance_scheduler.py +17 -7
- package/src/superlocalmemory/core/recall_pipeline.py +5 -3
- package/src/superlocalmemory/core/store_pipeline.py +153 -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/server.py +11 -30
- package/src/superlocalmemory/mcp/tools_core.py +4 -3
- package/src/superlocalmemory/mcp/tools_learning.py +2 -2
- package/src/superlocalmemory/retrieval/engine.py +53 -21
- package/src/superlocalmemory/retrieval/reranker.py +3 -4
- 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 +336 -59
- 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 +276 -105
- package/src/superlocalmemory/server/routes/learning_telemetry.py +153 -0
- 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 +64 -20
- package/src/superlocalmemory/server/unified_daemon.py +467 -40
- 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 +3 -6
- 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-entities.js +22 -22
- package/src/superlocalmemory/ui/js/od-graph.js +11 -4
- package/src/superlocalmemory/ui/js/od-memories.js +47 -5
- package/src/superlocalmemory/ui/js/od-mesh.js +23 -9
- package/src/superlocalmemory/ui/js/od-settings.js +113 -59
- 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)
|
|
@@ -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;
|
|
@@ -521,12 +521,9 @@
|
|
|
521
521
|
var act = el.dataset.odAct;
|
|
522
522
|
|
|
523
523
|
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 = '';
|
|
524
|
+
_st = Object.assign({}, _st, { typeFilter: el.dataset.type, page: 0 });
|
|
528
525
|
_refreshTypeChips(id, el.dataset.type);
|
|
529
|
-
|
|
526
|
+
_loadList(id);
|
|
530
527
|
return;
|
|
531
528
|
}
|
|
532
529
|
if (act === 'select-entity') {
|
|
@@ -546,8 +543,11 @@
|
|
|
546
543
|
|
|
547
544
|
container.addEventListener('input', function (e) {
|
|
548
545
|
if (e.target.dataset.odAct === 'ent-search') {
|
|
549
|
-
_st = Object.assign({}, _st, { search: e.target.value.trim() });
|
|
550
|
-
|
|
546
|
+
_st = Object.assign({}, _st, { search: e.target.value.trim(), page: 0 });
|
|
547
|
+
clearTimeout(_searchTimer);
|
|
548
|
+
_searchTimer = setTimeout(function () {
|
|
549
|
+
if (_st.rootId === id) _loadList(id);
|
|
550
|
+
}, 250);
|
|
551
551
|
}
|
|
552
552
|
});
|
|
553
553
|
}
|
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
var GRAPH_URL = '/api/graph';
|
|
17
17
|
var RECALL_URL = '/api/search'; /* POST {query, limit} -> {results:[...]} */
|
|
18
18
|
var MAX_NODES = 120; /* default budget; slider range 20-2000 */
|
|
19
|
+
/* Keep force-layout work bounded even when a user asks to render "All". */
|
|
20
|
+
var PHYSICS_MAX_NODES = 160;
|
|
21
|
+
var PRE_SETTLE_TICKS = 24;
|
|
19
22
|
|
|
20
23
|
/* ---- 16-colour categorical palette (Tableau-inspired, accessible) ---- */
|
|
21
24
|
var CAT_PALETTE = [
|
|
@@ -41,7 +44,7 @@
|
|
|
41
44
|
var raf = null, running = false, fitFrames = 0;
|
|
42
45
|
/* settle-freeze: stop the rAF loop once kinetic energy drops below SETTLE_KE */
|
|
43
46
|
var frames = 0, _ke = 0;
|
|
44
|
-
var SETTLE_MIN =
|
|
47
|
+
var SETTLE_MIN = 30, SETTLE_KE = 0.2, SETTLE_MAX_FRAMES = 180;
|
|
45
48
|
var NODES = [], LINKS = [], idx = {};
|
|
46
49
|
var tierVisible = { 1: true, 2: true, 3: true }; /* independent tier toggles */
|
|
47
50
|
var scale = 1, ox = 0, oy = 0, dpr = Math.max(1, window.devicePixelRatio || 1);
|
|
@@ -223,7 +226,11 @@
|
|
|
223
226
|
|
|
224
227
|
/* ---------------- physics (ported from design) ---------------- */
|
|
225
228
|
function tick() {
|
|
226
|
-
|
|
229
|
+
/* Force repulsion is O(n^2). Render every requested node, but only
|
|
230
|
+
simulate a stable bounded subset so the dashboard remains interactive
|
|
231
|
+
for existing installs with very large graphs. */
|
|
232
|
+
var vis = NODES.filter(visible).slice(0, PHYSICS_MAX_NODES), simulated = {}, i, j;
|
|
233
|
+
vis.forEach(function (n) { simulated[n.id] = true; });
|
|
227
234
|
for (i = 0; i < vis.length; i++) {
|
|
228
235
|
var a = vis[i];
|
|
229
236
|
for (j = i + 1; j < vis.length; j++) {
|
|
@@ -234,7 +241,7 @@
|
|
|
234
241
|
}
|
|
235
242
|
}
|
|
236
243
|
LINKS.forEach(function (l) {
|
|
237
|
-
if (!visLink(l)) return;
|
|
244
|
+
if (!visLink(l) || !simulated[l.s] || !simulated[l.t]) return;
|
|
238
245
|
var a = idx[l.s], b = idx[l.t];
|
|
239
246
|
var dx = b.x - a.x, dy = b.y - a.y, d = Math.sqrt(dx * dx + dy * dy) + 0.01;
|
|
240
247
|
var rest = (a.type === 'community' || b.type === 'community') ? 150 : 100;
|
|
@@ -557,7 +564,7 @@
|
|
|
557
564
|
running = false; draw(); updateCount(); updateLegend(); return;
|
|
558
565
|
}
|
|
559
566
|
readPalette(); resize(); updateCount(); updateLegend(); selected = null; setInspectorEmpty();
|
|
560
|
-
for (var i = 0; i <
|
|
567
|
+
for (var i = 0; i < PRE_SETTLE_TICKS; i++) tick(); /* bounded pre-settle keeps navigation responsive */
|
|
561
568
|
fitFrames = 0; frames = 0;
|
|
562
569
|
if (!running) { running = true; loop(); } else { wake(); }
|
|
563
570
|
}).catch(function (err) {
|
|
@@ -106,6 +106,8 @@
|
|
|
106
106
|
catCounts: {}, // pre-fetched per-category totals
|
|
107
107
|
tlRange: '30d',
|
|
108
108
|
cluLoaded: false, // reset in render() to avoid stale state on re-entry
|
|
109
|
+
searchQ: null,
|
|
110
|
+
requestSeq: 0,
|
|
109
111
|
};
|
|
110
112
|
|
|
111
113
|
// Known categories from the daemon (pre-fetched at render time)
|
|
@@ -119,7 +121,7 @@
|
|
|
119
121
|
var id = 'od-mem-' + Math.random().toString(36).slice(2, 8);
|
|
120
122
|
_st = Object.assign({}, _st, {
|
|
121
123
|
rootId: id, page: 0, category: null, sort: 'created',
|
|
122
|
-
memories: [], catCounts: {}, cluLoaded: false,
|
|
124
|
+
memories: [], catCounts: {}, cluLoaded: false, searchQ: null, requestSeq: 0,
|
|
123
125
|
});
|
|
124
126
|
// Clear per-id timeline cache for this render instance
|
|
125
127
|
_tlLoaded = {};
|
|
@@ -306,6 +308,8 @@
|
|
|
306
308
|
if (_st.scopeView && _st.scopeView !== 'mine') {
|
|
307
309
|
url += '&scope=' + encodeURIComponent(_st.scopeView);
|
|
308
310
|
}
|
|
311
|
+
var requestSeq = _st.requestSeq + 1;
|
|
312
|
+
_st = Object.assign({}, _st, { requestSeq: requestSeq });
|
|
309
313
|
var wrap = document.getElementById(id + '-tbl-wrap');
|
|
310
314
|
if (wrap) wrap.innerHTML = _loading('Loading memories…');
|
|
311
315
|
|
|
@@ -315,10 +319,12 @@
|
|
|
315
319
|
return r.json();
|
|
316
320
|
})
|
|
317
321
|
.then(function (d) {
|
|
322
|
+
if (_st.rootId !== id || _st.requestSeq !== requestSeq) return;
|
|
318
323
|
_st = Object.assign({}, _st, { memories: d.memories || [], total: d.total || 0 });
|
|
319
324
|
_renderTable(id, d);
|
|
320
325
|
})
|
|
321
326
|
.catch(function (err) {
|
|
327
|
+
if (_st.rootId !== id || _st.requestSeq !== requestSeq) return;
|
|
322
328
|
var w = document.getElementById(id + '-tbl-wrap');
|
|
323
329
|
if (w) w.innerHTML = '<div class="card-pad" style="color:var(--danger);text-align:center">' +
|
|
324
330
|
'Failed to load memories: ' + _esc(err.message) + '</div>';
|
|
@@ -442,22 +448,38 @@
|
|
|
442
448
|
var _searchTimer = null;
|
|
443
449
|
function _doSearch(id, q) {
|
|
444
450
|
clearTimeout(_searchTimer);
|
|
451
|
+
var requestSeq = _st.requestSeq + 1;
|
|
452
|
+
// /api/search is a full-corpus semantic query. It cannot truthfully retain
|
|
453
|
+
// the list endpoint's category/scope selection, so clear those controls
|
|
454
|
+
// before the request rather than leaving the table and chips disagreeing.
|
|
455
|
+
_st = Object.assign({}, _st, {
|
|
456
|
+
searchQ: q || null,
|
|
457
|
+
category: q ? null : _st.category,
|
|
458
|
+
scopeView: q ? 'mine' : _st.scopeView,
|
|
459
|
+
page: 0,
|
|
460
|
+
requestSeq: requestSeq,
|
|
461
|
+
});
|
|
462
|
+
if (q) _rebuildCatBar(id);
|
|
445
463
|
_searchTimer = setTimeout(function () {
|
|
446
|
-
|
|
464
|
+
if (_st.rootId !== id || _st.requestSeq !== requestSeq) return;
|
|
447
465
|
if (!q) { _loadMem(id); return; }
|
|
448
466
|
var wrap = document.getElementById(id + '-tbl-wrap');
|
|
449
467
|
if (wrap) wrap.innerHTML = _loading('Searching…');
|
|
450
468
|
fetch('/api/search', { method: 'POST', credentials: 'same-origin',
|
|
451
469
|
headers: { 'Content-Type': 'application/json' },
|
|
470
|
+
slmInvalidatesCache: false,
|
|
471
|
+
slmRequiresWriteAuth: false,
|
|
452
472
|
body: JSON.stringify({ query: q, limit: 50, window: _st.window || '' })
|
|
453
473
|
}).then(function (r) {
|
|
454
474
|
if (!r.ok) throw new Error('HTTP ' + r.status);
|
|
455
475
|
return r.json();
|
|
456
476
|
}).then(function (d) {
|
|
477
|
+
if (_st.rootId !== id || _st.requestSeq !== requestSeq) return;
|
|
457
478
|
var mems = (d.results || []).map(_normSearch);
|
|
458
479
|
_st = Object.assign({}, _st, { memories: mems, total: mems.length });
|
|
459
480
|
_renderTable(id, { memories: mems, total: mems.length, limit: mems.length, offset: 0 });
|
|
460
481
|
}).catch(function (err) {
|
|
482
|
+
if (_st.rootId !== id || _st.requestSeq !== requestSeq) return;
|
|
461
483
|
var w = document.getElementById(id + '-tbl-wrap');
|
|
462
484
|
if (w) w.innerHTML = '<div class="card-pad" style="color:var(--danger);text-align:center;' +
|
|
463
485
|
'padding:24px">Search failed: ' + _esc(err.message) + '</div>';
|
|
@@ -760,7 +782,13 @@
|
|
|
760
782
|
if (bar) bar.querySelectorAll('[data-od-act="cat"]').forEach(function (c) {
|
|
761
783
|
c.classList.toggle('on', c.dataset.cat === el.dataset.cat);
|
|
762
784
|
});
|
|
763
|
-
|
|
785
|
+
clearTimeout(_searchTimer);
|
|
786
|
+
_st = Object.assign({}, _st, {
|
|
787
|
+
category: el.dataset.cat || null, page: 0, searchQ: null,
|
|
788
|
+
requestSeq: _st.requestSeq + 1,
|
|
789
|
+
});
|
|
790
|
+
var searchInput = document.querySelector('#' + id + ' [data-od-act="search"]');
|
|
791
|
+
if (searchInput) searchInput.value = '';
|
|
764
792
|
_loadMem(id);
|
|
765
793
|
return;
|
|
766
794
|
}
|
|
@@ -778,7 +806,13 @@
|
|
|
778
806
|
if (sroot) sroot.querySelectorAll('[data-od-act="scope-view"]').forEach(function (b) {
|
|
779
807
|
b.classList.toggle('active', b.dataset.scope === el.dataset.scope);
|
|
780
808
|
});
|
|
781
|
-
|
|
809
|
+
clearTimeout(_searchTimer);
|
|
810
|
+
_st = Object.assign({}, _st, {
|
|
811
|
+
scopeView: el.dataset.scope, page: 0, searchQ: null,
|
|
812
|
+
requestSeq: _st.requestSeq + 1,
|
|
813
|
+
});
|
|
814
|
+
var scopedSearchInput = document.querySelector('#' + id + ' [data-od-act="search"]');
|
|
815
|
+
if (scopedSearchInput) scopedSearchInput.value = '';
|
|
782
816
|
_loadMem(id);
|
|
783
817
|
return;
|
|
784
818
|
}
|
|
@@ -791,7 +825,15 @@
|
|
|
791
825
|
if (act === 'close-drawer') { _closeDrawer(); return; }
|
|
792
826
|
if (act === 'pg') {
|
|
793
827
|
var pg = parseInt(el.dataset.page, 10);
|
|
794
|
-
if (!isNaN(pg)) {
|
|
828
|
+
if (!isNaN(pg)) {
|
|
829
|
+
clearTimeout(_searchTimer);
|
|
830
|
+
_st = Object.assign({}, _st, {
|
|
831
|
+
page: pg, searchQ: null, requestSeq: _st.requestSeq + 1,
|
|
832
|
+
});
|
|
833
|
+
var pagedSearchInput = document.querySelector('#' + id + ' [data-od-act="search"]');
|
|
834
|
+
if (pagedSearchInput) pagedSearchInput.value = '';
|
|
835
|
+
_loadMem(id);
|
|
836
|
+
}
|
|
795
837
|
return;
|
|
796
838
|
}
|
|
797
839
|
if (act === 'expand-cluster') { _expandCluster(id, el.dataset.cid); return; }
|