superlocalmemory 2.4.2 → 2.5.0

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/ui/app.js CHANGED
@@ -731,6 +731,11 @@ window.addEventListener('DOMContentLoaded', function() {
731
731
  loadProfiles();
732
732
  loadStats();
733
733
  loadGraph();
734
+
735
+ // v2.5 — Event Bus + Agent Registry
736
+ initEventStream();
737
+ loadEventStats();
738
+ loadAgents();
734
739
  });
735
740
 
736
741
  // ============================================================================
@@ -1161,3 +1166,423 @@ function handleSort(th) {
1161
1166
  var showScores = memories.length > 0 && typeof memories[0].score === 'number';
1162
1167
  renderMemoriesTable(memories, showScores);
1163
1168
  }
1169
+
1170
+ // ============================================================================
1171
+ // v2.5 — Live Event Stream (SSE)
1172
+ // ============================================================================
1173
+ // Security note: All dynamic values are escaped via escapeHtml() before DOM insertion.
1174
+ // Data originates from our own trusted local SQLite database (localhost only).
1175
+ // No external/untrusted user input reaches the DOM — same pattern as existing code above.
1176
+
1177
+ var _eventSource = null;
1178
+ var _eventStreamItems = [];
1179
+ var _maxEventStreamItems = 200;
1180
+
1181
+ function initEventStream() {
1182
+ try {
1183
+ _eventSource = new EventSource('/events/stream');
1184
+
1185
+ _eventSource.onopen = function() {
1186
+ var badge = document.getElementById('event-connection-status');
1187
+ if (badge) {
1188
+ badge.textContent = 'Connected';
1189
+ badge.className = 'badge bg-success me-2';
1190
+ }
1191
+ };
1192
+
1193
+ _eventSource.onmessage = function(e) {
1194
+ try {
1195
+ var event = JSON.parse(e.data);
1196
+ appendEventToStream(event);
1197
+ } catch (err) {
1198
+ // Ignore parse errors (keepalive comments)
1199
+ }
1200
+ };
1201
+
1202
+ _eventSource.onerror = function() {
1203
+ var badge = document.getElementById('event-connection-status');
1204
+ if (badge) {
1205
+ badge.textContent = 'Reconnecting...';
1206
+ badge.className = 'badge bg-warning me-2';
1207
+ }
1208
+ };
1209
+
1210
+ ['memory.created', 'memory.updated', 'memory.deleted', 'memory.recalled',
1211
+ 'agent.connected', 'agent.disconnected', 'graph.updated', 'pattern.learned'
1212
+ ].forEach(function(type) {
1213
+ _eventSource.addEventListener(type, function(e) {
1214
+ try {
1215
+ appendEventToStream(JSON.parse(e.data));
1216
+ } catch (err) { /* ignore */ }
1217
+ });
1218
+ });
1219
+ } catch (err) {
1220
+ console.log('SSE not available:', err);
1221
+ var badge = document.getElementById('event-connection-status');
1222
+ if (badge) {
1223
+ badge.textContent = 'Unavailable';
1224
+ badge.className = 'badge bg-secondary me-2';
1225
+ }
1226
+ }
1227
+ }
1228
+
1229
+ function appendEventToStream(event) {
1230
+ var container = document.getElementById('event-stream');
1231
+ if (!container) return;
1232
+
1233
+ if (_eventStreamItems.length === 0) {
1234
+ container.textContent = '';
1235
+ }
1236
+
1237
+ _eventStreamItems.push(event);
1238
+ if (_eventStreamItems.length > _maxEventStreamItems) {
1239
+ _eventStreamItems.shift();
1240
+ }
1241
+
1242
+ var filter = document.getElementById('event-type-filter');
1243
+ var filterValue = filter ? filter.value : '';
1244
+ if (filterValue && event.event_type !== filterValue) return;
1245
+
1246
+ var typeColors = {
1247
+ 'memory.created': 'text-success', 'memory.updated': 'text-info',
1248
+ 'memory.deleted': 'text-danger', 'memory.recalled': 'text-primary',
1249
+ 'agent.connected': 'text-warning', 'agent.disconnected': 'text-secondary',
1250
+ 'graph.updated': 'text-info', 'pattern.learned': 'text-success'
1251
+ };
1252
+ var typeIcons = {
1253
+ 'memory.created': 'bi-plus-circle', 'memory.updated': 'bi-pencil',
1254
+ 'memory.deleted': 'bi-trash', 'memory.recalled': 'bi-search',
1255
+ 'agent.connected': 'bi-plug', 'agent.disconnected': 'bi-plug',
1256
+ 'graph.updated': 'bi-diagram-3', 'pattern.learned': 'bi-lightbulb'
1257
+ };
1258
+
1259
+ var colorClass = typeColors[event.event_type] || 'text-muted';
1260
+ var iconClass = typeIcons[event.event_type] || 'bi-circle';
1261
+ var ts = event.timestamp ? new Date(event.timestamp).toLocaleTimeString() : '';
1262
+ var payload = event.payload || {};
1263
+ var preview = payload.content_preview || payload.agent_id || payload.agent_name || '';
1264
+ if (preview.length > 80) preview = preview.substring(0, 80) + '...';
1265
+
1266
+ // Build event line using safe DOM methods + escapeHtml for all dynamic content
1267
+ var div = document.createElement('div');
1268
+ div.className = 'event-line mb-1 pb-1 border-bottom border-opacity-25';
1269
+
1270
+ var timeSpan = document.createElement('small');
1271
+ timeSpan.className = 'text-muted';
1272
+ timeSpan.textContent = ts;
1273
+
1274
+ var icon = document.createElement('i');
1275
+ icon.className = 'bi ' + iconClass + ' ' + colorClass;
1276
+ icon.style.marginLeft = '6px';
1277
+
1278
+ var typeSpan = document.createElement('span');
1279
+ typeSpan.className = colorClass + ' fw-bold';
1280
+ typeSpan.style.marginLeft = '4px';
1281
+ typeSpan.textContent = event.event_type;
1282
+
1283
+ div.appendChild(timeSpan);
1284
+ div.appendChild(document.createTextNode(' '));
1285
+ div.appendChild(icon);
1286
+ div.appendChild(document.createTextNode(' '));
1287
+ div.appendChild(typeSpan);
1288
+ div.appendChild(document.createTextNode(' '));
1289
+
1290
+ if (event.memory_id) {
1291
+ var badge = document.createElement('span');
1292
+ badge.className = 'badge bg-secondary';
1293
+ badge.textContent = '#' + event.memory_id;
1294
+ div.appendChild(badge);
1295
+ div.appendChild(document.createTextNode(' '));
1296
+ }
1297
+
1298
+ var previewSpan = document.createElement('span');
1299
+ previewSpan.className = 'text-muted';
1300
+ previewSpan.textContent = preview;
1301
+ div.appendChild(previewSpan);
1302
+
1303
+ container.insertBefore(div, container.firstChild);
1304
+
1305
+ while (container.children.length > _maxEventStreamItems) {
1306
+ container.removeChild(container.lastChild);
1307
+ }
1308
+ }
1309
+
1310
+ function filterEvents() {
1311
+ var container = document.getElementById('event-stream');
1312
+ if (!container) return;
1313
+ container.textContent = '';
1314
+
1315
+ var filter = document.getElementById('event-type-filter');
1316
+ var filterValue = filter ? filter.value : '';
1317
+
1318
+ var filtered = filterValue
1319
+ ? _eventStreamItems.filter(function(e) { return e.event_type === filterValue; })
1320
+ : _eventStreamItems;
1321
+
1322
+ filtered.forEach(function(event) {
1323
+ appendEventToStream(event);
1324
+ });
1325
+ }
1326
+
1327
+ function clearEventStream() {
1328
+ _eventStreamItems = [];
1329
+ var container = document.getElementById('event-stream');
1330
+ if (container) {
1331
+ container.textContent = '';
1332
+ var placeholder = document.createElement('div');
1333
+ placeholder.className = 'text-muted text-center py-4';
1334
+ var pIcon = document.createElement('i');
1335
+ pIcon.className = 'bi bi-broadcast';
1336
+ pIcon.style.fontSize = '2rem';
1337
+ placeholder.appendChild(pIcon);
1338
+ var pText = document.createElement('p');
1339
+ pText.className = 'mt-2';
1340
+ pText.textContent = 'Event stream cleared. Waiting for new events...';
1341
+ placeholder.appendChild(pText);
1342
+ container.appendChild(placeholder);
1343
+ }
1344
+ }
1345
+
1346
+ async function loadEventStats() {
1347
+ try {
1348
+ var response = await fetch('/api/events/stats');
1349
+ var stats = await response.json();
1350
+ var el;
1351
+ el = document.getElementById('event-stat-total');
1352
+ if (el) el.textContent = (stats.total_events || 0).toLocaleString();
1353
+ el = document.getElementById('event-stat-24h');
1354
+ if (el) el.textContent = (stats.events_last_24h || 0).toLocaleString();
1355
+ el = document.getElementById('event-stat-listeners');
1356
+ if (el) el.textContent = (stats.listener_count || 0).toLocaleString();
1357
+ el = document.getElementById('event-stat-buffer');
1358
+ if (el) el.textContent = (stats.buffer_size || 0).toLocaleString();
1359
+ } catch (err) {
1360
+ console.log('Event stats not available:', err);
1361
+ }
1362
+ }
1363
+
1364
+ // ============================================================================
1365
+ // v2.5 — Connected Agents
1366
+ // ============================================================================
1367
+
1368
+ async function loadAgents() {
1369
+ try {
1370
+ var response = await fetch('/api/agents');
1371
+ var data = await response.json();
1372
+ var agents = data.agents || [];
1373
+ var stats = data.stats || {};
1374
+
1375
+ var el;
1376
+ el = document.getElementById('agent-stat-total');
1377
+ if (el) el.textContent = (stats.total_agents || 0).toLocaleString();
1378
+ el = document.getElementById('agent-stat-active');
1379
+ if (el) el.textContent = (stats.active_last_24h || 0).toLocaleString();
1380
+ el = document.getElementById('agent-stat-writes');
1381
+ if (el) el.textContent = (stats.total_writes || 0).toLocaleString();
1382
+ el = document.getElementById('agent-stat-recalls');
1383
+ if (el) el.textContent = (stats.total_recalls || 0).toLocaleString();
1384
+
1385
+ var container = document.getElementById('agents-list');
1386
+ if (!container) return;
1387
+
1388
+ if (agents.length === 0) {
1389
+ container.textContent = '';
1390
+ var empty = document.createElement('div');
1391
+ empty.className = 'text-muted text-center py-4';
1392
+ var emptyIcon = document.createElement('i');
1393
+ emptyIcon.className = 'bi bi-robot';
1394
+ emptyIcon.style.fontSize = '2rem';
1395
+ empty.appendChild(emptyIcon);
1396
+ var emptyText = document.createElement('p');
1397
+ emptyText.className = 'mt-2';
1398
+ emptyText.textContent = 'No agents registered yet. Agents appear automatically when they connect via MCP, CLI, or REST.';
1399
+ empty.appendChild(emptyText);
1400
+ container.appendChild(empty);
1401
+ loadTrustOverview();
1402
+ return;
1403
+ }
1404
+
1405
+ // Build agent table using safe DOM methods
1406
+ var table = document.createElement('table');
1407
+ table.className = 'table table-hover table-sm';
1408
+ var thead = document.createElement('thead');
1409
+ var headerRow = document.createElement('tr');
1410
+ ['Agent', 'Protocol', 'Trust', 'Writes', 'Recalls', 'Last Seen'].forEach(function(h) {
1411
+ var th = document.createElement('th');
1412
+ th.textContent = h;
1413
+ headerRow.appendChild(th);
1414
+ });
1415
+ thead.appendChild(headerRow);
1416
+ table.appendChild(thead);
1417
+
1418
+ var tbody = document.createElement('tbody');
1419
+ agents.forEach(function(agent) {
1420
+ var tr = document.createElement('tr');
1421
+
1422
+ // Agent name cell
1423
+ var tdName = document.createElement('td');
1424
+ var strong = document.createElement('strong');
1425
+ strong.textContent = agent.agent_name || agent.agent_id;
1426
+ tdName.appendChild(strong);
1427
+ tdName.appendChild(document.createElement('br'));
1428
+ var smallId = document.createElement('small');
1429
+ smallId.className = 'text-muted';
1430
+ smallId.textContent = agent.agent_id;
1431
+ tdName.appendChild(smallId);
1432
+ tr.appendChild(tdName);
1433
+
1434
+ // Protocol badge
1435
+ var tdProto = document.createElement('td');
1436
+ var protoBadge = document.createElement('span');
1437
+ var protocolColors = {
1438
+ 'mcp': 'bg-primary', 'cli': 'bg-success', 'rest': 'bg-info',
1439
+ 'python': 'bg-secondary', 'a2a': 'bg-warning'
1440
+ };
1441
+ protoBadge.className = 'badge ' + (protocolColors[agent.protocol] || 'bg-secondary');
1442
+ protoBadge.textContent = agent.protocol;
1443
+ tdProto.appendChild(protoBadge);
1444
+ tr.appendChild(tdProto);
1445
+
1446
+ // Trust score
1447
+ var tdTrust = document.createElement('td');
1448
+ var trustScore = agent.trust_score != null ? agent.trust_score : 1.0;
1449
+ tdTrust.className = trustScore < 0.7 ? 'text-danger fw-bold'
1450
+ : trustScore < 0.9 ? 'text-warning fw-bold' : 'text-success fw-bold';
1451
+ tdTrust.textContent = trustScore.toFixed(2);
1452
+ tr.appendChild(tdTrust);
1453
+
1454
+ // Writes
1455
+ var tdW = document.createElement('td');
1456
+ tdW.textContent = agent.memories_written || 0;
1457
+ tr.appendChild(tdW);
1458
+
1459
+ // Recalls
1460
+ var tdR = document.createElement('td');
1461
+ tdR.textContent = agent.memories_recalled || 0;
1462
+ tr.appendChild(tdR);
1463
+
1464
+ // Last seen
1465
+ var tdLast = document.createElement('td');
1466
+ var lastSmall = document.createElement('small');
1467
+ lastSmall.textContent = agent.last_seen ? new Date(agent.last_seen).toLocaleString() : 'Never';
1468
+ tdLast.appendChild(lastSmall);
1469
+ tr.appendChild(tdLast);
1470
+
1471
+ tbody.appendChild(tr);
1472
+ });
1473
+ table.appendChild(tbody);
1474
+
1475
+ container.textContent = '';
1476
+ container.appendChild(table);
1477
+
1478
+ loadTrustOverview();
1479
+
1480
+ } catch (err) {
1481
+ console.log('Agents not available:', err);
1482
+ var container = document.getElementById('agents-list');
1483
+ if (container) {
1484
+ container.textContent = '';
1485
+ var msg = document.createElement('small');
1486
+ msg.className = 'text-muted';
1487
+ msg.textContent = 'Agent registry not available. This feature requires v2.5+.';
1488
+ container.appendChild(msg);
1489
+ }
1490
+ }
1491
+ }
1492
+
1493
+ async function loadTrustOverview() {
1494
+ try {
1495
+ var response = await fetch('/api/trust/stats');
1496
+ var stats = await response.json();
1497
+ var container = document.getElementById('trust-overview');
1498
+ if (!container) return;
1499
+
1500
+ container.textContent = '';
1501
+ var row = document.createElement('div');
1502
+ row.className = 'row g-3';
1503
+
1504
+ // Total signals card
1505
+ var col1 = document.createElement('div');
1506
+ col1.className = 'col-md-4';
1507
+ var card1 = document.createElement('div');
1508
+ card1.className = 'border rounded p-3 text-center';
1509
+ var val1 = document.createElement('div');
1510
+ val1.className = 'fs-4 fw-bold';
1511
+ val1.textContent = (stats.total_signals || 0).toLocaleString();
1512
+ card1.appendChild(val1);
1513
+ var lbl1 = document.createElement('small');
1514
+ lbl1.className = 'text-muted';
1515
+ lbl1.textContent = 'Total Signals Collected';
1516
+ card1.appendChild(lbl1);
1517
+ col1.appendChild(card1);
1518
+ row.appendChild(col1);
1519
+
1520
+ // Avg trust card
1521
+ var col2 = document.createElement('div');
1522
+ col2.className = 'col-md-4';
1523
+ var card2 = document.createElement('div');
1524
+ card2.className = 'border rounded p-3 text-center';
1525
+ var val2 = document.createElement('div');
1526
+ val2.className = 'fs-4 fw-bold';
1527
+ val2.textContent = (stats.avg_trust_score || 1.0).toFixed(3);
1528
+ card2.appendChild(val2);
1529
+ var lbl2 = document.createElement('small');
1530
+ lbl2.className = 'text-muted';
1531
+ lbl2.textContent = 'Average Trust Score';
1532
+ card2.appendChild(lbl2);
1533
+ col2.appendChild(card2);
1534
+ row.appendChild(col2);
1535
+
1536
+ // Enforcement card
1537
+ var col3 = document.createElement('div');
1538
+ col3.className = 'col-md-4';
1539
+ var card3 = document.createElement('div');
1540
+ card3.className = 'border rounded p-3 text-center';
1541
+ var val3 = document.createElement('div');
1542
+ val3.className = 'fs-4 fw-bold text-info';
1543
+ val3.textContent = stats.enforcement || 'disabled';
1544
+ card3.appendChild(val3);
1545
+ var lbl3 = document.createElement('small');
1546
+ lbl3.className = 'text-muted';
1547
+ lbl3.textContent = 'Enforcement Status';
1548
+ card3.appendChild(lbl3);
1549
+ col3.appendChild(card3);
1550
+ row.appendChild(col3);
1551
+
1552
+ container.appendChild(row);
1553
+
1554
+ // Signal breakdown
1555
+ if (stats.by_signal_type && Object.keys(stats.by_signal_type).length > 0) {
1556
+ var breakdownDiv = document.createElement('div');
1557
+ breakdownDiv.className = 'col-12 mt-3';
1558
+ var h6 = document.createElement('h6');
1559
+ h6.textContent = 'Signal Breakdown';
1560
+ breakdownDiv.appendChild(h6);
1561
+ var badgeWrap = document.createElement('div');
1562
+ badgeWrap.className = 'd-flex flex-wrap gap-2';
1563
+ Object.keys(stats.by_signal_type).forEach(function(type) {
1564
+ var count = stats.by_signal_type[type];
1565
+ var signalClass = (type.indexOf('high_volume') >= 0 || type.indexOf('quick_delete') >= 0)
1566
+ ? 'bg-danger' : (type.indexOf('recalled') >= 0 || type.indexOf('high_importance') >= 0)
1567
+ ? 'bg-success' : 'bg-secondary';
1568
+ var b = document.createElement('span');
1569
+ b.className = 'badge ' + signalClass;
1570
+ b.textContent = type + ': ' + count;
1571
+ badgeWrap.appendChild(b);
1572
+ });
1573
+ breakdownDiv.appendChild(badgeWrap);
1574
+ container.appendChild(breakdownDiv);
1575
+ }
1576
+
1577
+ } catch (err) {
1578
+ console.log('Trust stats not available:', err);
1579
+ var container = document.getElementById('trust-overview');
1580
+ if (container) {
1581
+ container.textContent = '';
1582
+ var msg = document.createElement('small');
1583
+ msg.className = 'text-muted';
1584
+ msg.textContent = 'Trust scoring data will appear here once agents interact with memory.';
1585
+ container.appendChild(msg);
1586
+ }
1587
+ }
1588
+ }
package/ui/index.html CHANGED
@@ -454,6 +454,16 @@
454
454
  <i class="bi bi-clock-history"></i> Timeline
455
455
  </button>
456
456
  </li>
457
+ <li class="nav-item">
458
+ <button class="nav-link" id="events-tab" data-bs-toggle="tab" data-bs-target="#events-pane">
459
+ <i class="bi bi-broadcast"></i> Live Events
460
+ </button>
461
+ </li>
462
+ <li class="nav-item">
463
+ <button class="nav-link" id="agents-tab" data-bs-toggle="tab" data-bs-target="#agents-pane">
464
+ <i class="bi bi-robot"></i> Agents
465
+ </button>
466
+ </li>
457
467
  <li class="nav-item">
458
468
  <button class="nav-link" id="settings-tab" data-bs-toggle="tab" data-bs-target="#settings-pane">
459
469
  <i class="bi bi-gear"></i> Settings
@@ -577,6 +587,129 @@
577
587
  </div>
578
588
  </div>
579
589
 
590
+ <!-- Live Events (v2.5) -->
591
+ <div class="tab-pane fade" id="events-pane">
592
+ <div class="card p-3 mb-3">
593
+ <div class="row align-items-center mb-3">
594
+ <div class="col-md-6">
595
+ <h5 class="mb-0"><i class="bi bi-broadcast"></i> Live Event Stream</h5>
596
+ <small class="text-muted">Real-time memory operations as they happen</small>
597
+ </div>
598
+ <div class="col-md-6 text-end">
599
+ <span id="event-connection-status" class="badge bg-secondary me-2">Connecting...</span>
600
+ <select class="form-select form-select-sm d-inline-block w-auto" id="event-type-filter" onchange="filterEvents()">
601
+ <option value="">All Events</option>
602
+ <option value="memory.created">Memory Created</option>
603
+ <option value="memory.updated">Memory Updated</option>
604
+ <option value="memory.deleted">Memory Deleted</option>
605
+ <option value="memory.recalled">Memory Recalled</option>
606
+ <option value="agent.connected">Agent Connected</option>
607
+ </select>
608
+ <button class="btn btn-sm btn-outline-secondary ms-1" onclick="clearEventStream()">
609
+ <i class="bi bi-trash"></i> Clear
610
+ </button>
611
+ </div>
612
+ </div>
613
+ <!-- Event Stats Row -->
614
+ <div class="row g-2 mb-3" id="event-stats-row">
615
+ <div class="col-md-3">
616
+ <div class="border rounded p-2 text-center">
617
+ <div class="fw-bold" id="event-stat-total">0</div>
618
+ <small class="text-muted">Total Events</small>
619
+ </div>
620
+ </div>
621
+ <div class="col-md-3">
622
+ <div class="border rounded p-2 text-center">
623
+ <div class="fw-bold" id="event-stat-24h">0</div>
624
+ <small class="text-muted">Last 24h</small>
625
+ </div>
626
+ </div>
627
+ <div class="col-md-3">
628
+ <div class="border rounded p-2 text-center">
629
+ <div class="fw-bold" id="event-stat-listeners">0</div>
630
+ <small class="text-muted">Listeners</small>
631
+ </div>
632
+ </div>
633
+ <div class="col-md-3">
634
+ <div class="border rounded p-2 text-center">
635
+ <div class="fw-bold" id="event-stat-buffer">0</div>
636
+ <small class="text-muted">In Buffer</small>
637
+ </div>
638
+ </div>
639
+ </div>
640
+ <!-- Event Stream -->
641
+ <div id="event-stream" style="max-height: 500px; overflow-y: auto; font-family: 'Courier New', monospace; font-size: 0.85rem; background: var(--bs-body-bg); border: 1px solid var(--bs-border-color); border-radius: 8px; padding: 12px;">
642
+ <div class="text-muted text-center py-4">
643
+ <i class="bi bi-broadcast" style="font-size: 2rem;"></i>
644
+ <p class="mt-2">Waiting for events...</p>
645
+ <small>Events will appear here in real-time as memories are created, updated, or deleted.</small>
646
+ </div>
647
+ </div>
648
+ </div>
649
+ </div>
650
+
651
+ <!-- Connected Agents (v2.5) -->
652
+ <div class="tab-pane fade" id="agents-pane">
653
+ <div class="card p-3 mb-3">
654
+ <div class="row align-items-center mb-3">
655
+ <div class="col-md-6">
656
+ <h5 class="mb-0"><i class="bi bi-robot"></i> Connected Agents</h5>
657
+ <small class="text-muted">AI tools that interact with your memory</small>
658
+ </div>
659
+ <div class="col-md-6 text-end">
660
+ <button class="btn btn-sm btn-outline-primary" onclick="loadAgents()">
661
+ <i class="bi bi-arrow-clockwise"></i> Refresh
662
+ </button>
663
+ </div>
664
+ </div>
665
+ <!-- Agent Stats -->
666
+ <div class="row g-2 mb-3" id="agent-stats-row">
667
+ <div class="col-md-3">
668
+ <div class="border rounded p-2 text-center">
669
+ <div class="fw-bold" id="agent-stat-total">0</div>
670
+ <small class="text-muted">Total Agents</small>
671
+ </div>
672
+ </div>
673
+ <div class="col-md-3">
674
+ <div class="border rounded p-2 text-center">
675
+ <div class="fw-bold" id="agent-stat-active">0</div>
676
+ <small class="text-muted">Active (24h)</small>
677
+ </div>
678
+ </div>
679
+ <div class="col-md-3">
680
+ <div class="border rounded p-2 text-center">
681
+ <div class="fw-bold" id="agent-stat-writes">0</div>
682
+ <small class="text-muted">Total Writes</small>
683
+ </div>
684
+ </div>
685
+ <div class="col-md-3">
686
+ <div class="border rounded p-2 text-center">
687
+ <div class="fw-bold" id="agent-stat-recalls">0</div>
688
+ <small class="text-muted">Total Recalls</small>
689
+ </div>
690
+ </div>
691
+ </div>
692
+ <!-- Agent List -->
693
+ <div id="agents-list" class="table-responsive">
694
+ <div class="loading">
695
+ <div class="spinner-border text-primary" role="status"></div>
696
+ <div>Loading agents...</div>
697
+ </div>
698
+ </div>
699
+ </div>
700
+ <!-- Trust Overview -->
701
+ <div class="card p-3">
702
+ <h5 class="mb-3"><i class="bi bi-shield-check"></i> Trust Scoring <span class="badge bg-info">Silent Collection</span></h5>
703
+ <p class="text-muted small mb-3">Trust scores are being collected silently. No enforcement in v2.5 — scores will affect recall ranking in v2.6.</p>
704
+ <div id="trust-overview">
705
+ <div class="loading">
706
+ <div class="spinner-border text-primary" role="status"></div>
707
+ <div>Loading trust data...</div>
708
+ </div>
709
+ </div>
710
+ </div>
711
+ </div>
712
+
580
713
  <!-- Settings & Backup -->
581
714
  <div class="tab-pane fade" id="settings-pane">
582
715
  <!-- Profile Management -->
@@ -696,7 +829,20 @@
696
829
  <!-- D3.js -->
697
830
  <script src="https://d3js.org/d3.v7.min.js"></script>
698
831
 
699
- <script src="static/app.js"></script>
832
+ <!-- Modular JS (v2.5 — split from monolith app.js) -->
833
+ <script src="static/js/core.js"></script>
834
+ <script src="static/js/graph.js"></script>
835
+ <script src="static/js/memories.js"></script>
836
+ <script src="static/js/search.js"></script>
837
+ <script src="static/js/modal.js"></script>
838
+ <script src="static/js/clusters.js"></script>
839
+ <script src="static/js/patterns.js"></script>
840
+ <script src="static/js/timeline.js"></script>
841
+ <script src="static/js/profiles.js"></script>
842
+ <script src="static/js/settings.js"></script>
843
+ <script src="static/js/events.js"></script>
844
+ <script src="static/js/agents.js"></script>
845
+ <script src="static/js/init.js"></script>
700
846
 
701
847
  <footer>
702
848
  <p>SuperLocalMemory V2 by <a href="https://github.com/varun369">Varun Pratap Bhardwaj</a></p>