prism-mcp-server 4.6.1 β†’ 5.2.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.
@@ -492,6 +492,14 @@ export function renderDashboardHTML(version) {
492
492
  border: 1px solid rgba(245,158,11,0.3); border-radius: 6px; cursor: pointer; transition: all 0.2s;
493
493
  }
494
494
  .ttl-save-btn:hover { background: rgba(245,158,11,0.3); }
495
+ .node-editor-panel {
496
+ background: var(--bg-card);
497
+ border: 1px solid var(--border-glow);
498
+ border-radius: 8px;
499
+ padding: 1rem;
500
+ margin-top: 1rem;
501
+ display: none;
502
+ }
495
503
  </style>
496
504
  </head>
497
505
  <body>
@@ -593,6 +601,53 @@ export function renderDashboardHTML(version) {
593
601
  <div style="font-size:0.7rem;color:var(--text-muted);margin-top:0.4rem">0 = disabled. Min 7 days. Rollups are never expired.</div>
594
602
  </div>
595
603
 
604
+ <!-- Universal History Import (v5.2) -->
605
+ <div class="card" id="importCard" style="display:none">
606
+ <div class="card-title"><span class="dot" style="background:var(--accent-cyan)"></span> Import History πŸ“₯</div>
607
+ <div style="margin-bottom:0.75rem">
608
+ <label style="font-size:0.78rem;color:var(--text-muted);display:block;margin-bottom:0.3rem">Source File</label>
609
+ <div style="display:flex;gap:0.4rem;align-items:center">
610
+ <input type="text" id="importPath" class="ttl-input" style="flex:1;text-align:left;font-size:0.82rem;padding:0.45rem 0.65rem" placeholder="/path/to/conversations.jsonl">
611
+ <input type="file" id="importFileInput" accept=".jsonl,.json,.ndjson" style="display:none">
612
+ <button class="lc-btn compact" onclick="document.getElementById('importFileInput').click()" style="flex:none;padding:0.45rem 0.75rem;font-size:0.82rem;white-space:nowrap" title="Choose a file from your computer">
613
+ πŸ“‚ Browse
614
+ </button>
615
+ <button class="lc-btn" onclick="clearImportFile()" id="importClearBtn" style="flex:none;padding:0.45rem 0.55rem;font-size:0.82rem;display:none;background:rgba(244,63,94,0.15);border-color:rgba(244,63,94,0.3);color:var(--accent-rose)" title="Clear selection">
616
+ βœ•
617
+ </button>
618
+ </div>
619
+ <div id="importFileInfo" style="display:none;margin-top:0.35rem;font-size:0.72rem;color:var(--accent-cyan)"></div>
620
+ </div>
621
+ <div style="display:flex;gap:0.5rem;margin-bottom:0.75rem;flex-wrap:wrap">
622
+ <div style="flex:1;min-width:120px">
623
+ <label style="font-size:0.78rem;color:var(--text-muted);display:block;margin-bottom:0.3rem">Format</label>
624
+ <select id="importFormat" class="ttl-input" style="width:100%;text-align:left;font-size:0.82rem;padding:0.35rem 0.5rem;cursor:pointer">
625
+ <option value="">Auto-detect</option>
626
+ <option value="claude">Claude Code (.jsonl)</option>
627
+ <option value="gemini">Gemini (.json)</option>
628
+ <option value="openai">OpenAI (.json)</option>
629
+ </select>
630
+ </div>
631
+ <div style="flex:1;min-width:120px">
632
+ <label style="font-size:0.78rem;color:var(--text-muted);display:block;margin-bottom:0.3rem">Target Project</label>
633
+ <input type="text" id="importProject" class="ttl-input" style="width:100%;text-align:left;font-size:0.82rem;padding:0.45rem 0.65rem" placeholder="(auto from file)">
634
+ </div>
635
+ </div>
636
+ <div style="display:flex;gap:0.5rem;align-items:center">
637
+ <button class="lc-btn compact" id="importBtn" onclick="runImport(false)" style="flex:1">
638
+ πŸ“₯ Import
639
+ </button>
640
+ <button class="lc-btn export" id="importDryBtn" onclick="runImport(true)" style="flex:1" title="Validate without writing to storage">
641
+ πŸ§ͺ Dry Run
642
+ </button>
643
+ </div>
644
+ <div id="importResult" style="display:none;margin-top:0.75rem;padding:0.65rem 0.85rem;border-radius:var(--radius-sm);font-size:0.82rem;line-height:1.5"></div>
645
+ <div style="font-size:0.68rem;color:var(--text-muted);margin-top:0.5rem">
646
+ Click <strong>Browse</strong> to pick a file, or type a server-side path.<br>
647
+ Supports Claude Code (.jsonl), Gemini (.json), and OpenAI (.json).
648
+ </div>
649
+ </div>
650
+
596
651
  <div class="card" id="briefingCard" style="display:none">
597
652
  <div class="card-title"><span class="dot" style="background:var(--accent-amber)"></span> Morning Briefing πŸŒ…</div>
598
653
  <div class="briefing-text" id="briefingText"></div>
@@ -608,14 +663,48 @@ export function renderDashboardHTML(version) {
608
663
  <!-- Right Column -->
609
664
  <div class="grid" style="align-content: start;">
610
665
 
611
- <!-- Neural Graph (v2.3.0) -->
666
+ <!-- Neural Graph (v2.3.0 / v5.1) -->
612
667
  <div class="card">
613
668
  <div class="card-title">
614
669
  <span class="dot" style="background:var(--accent-blue)"></span>
615
670
  Neural Graph πŸ•ΈοΈ
616
671
  <button onclick="loadGraph()" class="refresh-btn">↻</button>
617
672
  </div>
673
+
674
+ <!-- v5.1 Graph Filters -->
675
+ <div style="display:flex; gap:0.5rem; margin-bottom:1rem; flex-wrap:wrap;">
676
+ <select id="graphProjectFilter" class="input-modern" style="min-width:120px; font-size:0.75rem; padding:0.3rem 0.5rem" onchange="loadGraph()">
677
+ <option value="">All Projects</option>
678
+ </select>
679
+ <select id="graphDaysFilter" class="input-modern" style="font-size:0.75rem; padding:0.3rem 0.5rem" onchange="loadGraph()">
680
+ <option value="">All Time</option>
681
+ <option value="7">Last 7 Days</option>
682
+ <option value="30">Last 30 Days</option>
683
+ <option value="90">Last 90 Days</option>
684
+ </select>
685
+ <select id="graphImportanceFilter" class="input-modern" style="font-size:0.75rem; padding:0.3rem 0.5rem" onchange="loadGraph()">
686
+ <option value="">Any Importance</option>
687
+ <option value="5">Importance &gt;= 5</option>
688
+ <option value="7">Graduated (&gt;= 7)</option>
689
+ </select>
690
+ </div>
691
+
618
692
  <div id="network-container">Loading nodes...</div>
693
+
694
+ <!-- v5.1 Node Editor Panel -->
695
+ <div id="nodeEditorPanel" class="node-editor-panel">
696
+ <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:0.8rem;">
697
+ <h4 id="nodeEditorTitle" style="margin:0; font-size:0.9rem; color:var(--text-primary);">Node Name</h4>
698
+ <span id="nodeEditorGroup" class="badge">category</span>
699
+ </div>
700
+
701
+ <label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:0.3rem">Rename (Or leave empty to delete)</label>
702
+ <div style="display:flex; gap:0.5rem;">
703
+ <input type="text" id="nodeEditorInput" class="input-modern" style="flex:1; font-size:0.8rem; padding:0.3rem 0.6rem" placeholder="New keyword name...">
704
+ <button onclick="submitNodeEdit()" class="btn-modern" style="padding:0.3rem 0.8rem; font-size:0.8rem">Apply</button>
705
+ <button onclick="document.getElementById('nodeEditorPanel').style.display='none'" class="btn-modern" style="background:transparent; border-color:var(--border-subtle); padding:0.3rem 0.8rem; font-size:0.8rem">Cancel</button>
706
+ </div>
707
+ </div>
619
708
  </div>
620
709
 
621
710
  <!-- Time Travel -->
@@ -1079,6 +1168,12 @@ Example:\n## Dev Rules\n- Always write tests first\n- Use TypeScript strict mode
1079
1168
  if (data.projects && data.projects.length > 0) {
1080
1169
  select.innerHTML = '<option value="">β€” Select a project β€”</option>' +
1081
1170
  data.projects.map(function(p) { return '<option value="' + p + '">' + p + '</option>'; }).join('');
1171
+
1172
+ var gp = document.getElementById('graphProjectFilter');
1173
+ if (gp) {
1174
+ gp.innerHTML = '<option value="">All Projects</option>' +
1175
+ data.projects.map(function(p) { return '<option value="' + p + '">' + p + '</option>'; }).join('');
1176
+ }
1082
1177
  } else {
1083
1178
  select.innerHTML = '<option value="">No projects found</option>';
1084
1179
  }
@@ -1232,9 +1327,10 @@ Example:\n## Dev Rules\n- Always write tests first\n- Use TypeScript strict mode
1232
1327
  document.getElementById('content').className = 'grid grid-main fade-in';
1233
1328
  document.getElementById('content').style.display = 'grid';
1234
1329
 
1235
- // v3.1: Analytics + Lifecycle Controls
1330
+ // v3.1: Analytics + Lifecycle Controls + Import
1236
1331
  document.getElementById('analyticsCard').style.display = 'block';
1237
1332
  document.getElementById('lifecycleCard').style.display = 'block';
1333
+ document.getElementById('importCard').style.display = 'block';
1238
1334
  loadAnalytics(project);
1239
1335
  loadRetention(project);
1240
1336
 
@@ -1355,6 +1451,122 @@ Example:\n## Dev Rules\n- Always write tests first\n- Use TypeScript strict mode
1355
1451
  }
1356
1452
  }
1357
1453
 
1454
+ // ─── v5.2: Universal History Import ───────────────────────────────
1455
+
1456
+ // Track the picked file for upload mode
1457
+ var _importPickedFile = null;
1458
+
1459
+ document.getElementById('importFileInput').addEventListener('change', function(e) {
1460
+ var file = e.target.files[0];
1461
+ if (!file) return;
1462
+ _importPickedFile = file;
1463
+ var pathInput = document.getElementById('importPath');
1464
+ pathInput.value = file.name;
1465
+ document.getElementById('importClearBtn').style.display = 'inline-flex';
1466
+ var infoEl = document.getElementById('importFileInfo');
1467
+ var sizeKB = (file.size / 1024).toFixed(1);
1468
+ var sizeMB = (file.size / (1024 * 1024)).toFixed(1);
1469
+ infoEl.textContent = 'πŸ“„ ' + file.name + ' (' + (file.size > 1048576 ? sizeMB + ' MB' : sizeKB + ' KB') + ')';
1470
+ infoEl.style.display = 'block';
1471
+
1472
+ // Auto-detect format from extension
1473
+ var fmt = document.getElementById('importFormat');
1474
+ if (file.name.endsWith('.jsonl') || file.name.endsWith('.ndjson')) {
1475
+ fmt.value = 'claude';
1476
+ } else if (file.name.toLowerCase().includes('gemini')) {
1477
+ fmt.value = 'gemini';
1478
+ } else if (file.name.toLowerCase().includes('openai') || file.name.toLowerCase().includes('chatgpt')) {
1479
+ fmt.value = 'openai';
1480
+ } else {
1481
+ fmt.value = '';
1482
+ }
1483
+ });
1484
+
1485
+ function clearImportFile() {
1486
+ _importPickedFile = null;
1487
+ document.getElementById('importPath').value = '';
1488
+ document.getElementById('importFileInput').value = '';
1489
+ document.getElementById('importClearBtn').style.display = 'none';
1490
+ document.getElementById('importFileInfo').style.display = 'none';
1491
+ document.getElementById('importResult').style.display = 'none';
1492
+ document.getElementById('importFormat').value = '';
1493
+ }
1494
+
1495
+ async function runImport(dryRun) {
1496
+ var filePath = document.getElementById('importPath').value.trim();
1497
+ if (!filePath && !_importPickedFile) { showToast('❌ Pick a file or enter a path', true); return; }
1498
+
1499
+ var format = document.getElementById('importFormat').value || undefined;
1500
+ var project = document.getElementById('importProject').value.trim() || undefined;
1501
+ var importBtn = document.getElementById('importBtn');
1502
+ var dryBtn = document.getElementById('importDryBtn');
1503
+ var resultEl = document.getElementById('importResult');
1504
+
1505
+ importBtn.disabled = true;
1506
+ dryBtn.disabled = true;
1507
+ var activeBtn = dryRun ? dryBtn : importBtn;
1508
+ var origText = activeBtn.innerHTML;
1509
+ activeBtn.innerHTML = dryRun ? 'πŸ”„ Validating...' : 'πŸ”„ Importing...';
1510
+
1511
+ resultEl.style.display = 'block';
1512
+ resultEl.style.background = 'rgba(139,92,246,0.1)';
1513
+ resultEl.style.border = '1px solid rgba(139,92,246,0.25)';
1514
+ resultEl.style.color = 'var(--accent-purple)';
1515
+ resultEl.innerHTML = '<span class="spinner" style="width:16px;height:16px;border-width:2px;margin-right:0.4rem;vertical-align:middle"></span> ' +
1516
+ (dryRun ? 'Validating file...' : 'Importing turns...');
1517
+
1518
+ try {
1519
+ var endpoint, body, headers;
1520
+
1521
+ if (_importPickedFile) {
1522
+ // Upload mode: read file and send as base64
1523
+ var content = await _importPickedFile.text();
1524
+ endpoint = '/api/import-upload';
1525
+ headers = {'Content-Type':'application/json'};
1526
+ body = JSON.stringify({
1527
+ filename: _importPickedFile.name,
1528
+ content: content,
1529
+ format: format,
1530
+ project: project,
1531
+ dryRun: dryRun
1532
+ });
1533
+ } else {
1534
+ // Path mode: just send the server-side path
1535
+ endpoint = '/api/import';
1536
+ headers = {'Content-Type':'application/json'};
1537
+ body = JSON.stringify({ path: filePath, format: format, project: project, dryRun: dryRun });
1538
+ }
1539
+
1540
+ var res = await fetch(endpoint, { method: 'POST', headers: headers, body: body });
1541
+ var d = await res.json();
1542
+ if (res.ok && d.ok) {
1543
+ resultEl.style.background = 'rgba(16,185,129,0.1)';
1544
+ resultEl.style.border = '1px solid rgba(16,185,129,0.25)';
1545
+ resultEl.style.color = 'var(--accent-green)';
1546
+ resultEl.innerHTML = 'βœ… ' + escapeHtml(d.message) +
1547
+ '<div style="margin-top:0.4rem;font-size:0.75rem;color:var(--text-muted)">' +
1548
+ 'Conversations: ' + (d.conversationCount || 0) + ' Β· Turns: ' + (d.successCount || 0) +
1549
+ (d.skipCount ? ' Β· Skipped: ' + d.skipCount : '') +
1550
+ (d.failCount ? ' Β· Failed: ' + d.failCount : '') + '</div>';
1551
+ if (!dryRun) { showToast('βœ“ Import complete'); loadProject(); }
1552
+ } else {
1553
+ resultEl.style.background = 'rgba(244,63,94,0.1)';
1554
+ resultEl.style.border = '1px solid rgba(244,63,94,0.25)';
1555
+ resultEl.style.color = 'var(--accent-rose)';
1556
+ resultEl.innerHTML = '❌ ' + escapeHtml(d.error || 'Import failed');
1557
+ }
1558
+ } catch(e) {
1559
+ resultEl.style.background = 'rgba(244,63,94,0.1)';
1560
+ resultEl.style.border = '1px solid rgba(244,63,94,0.25)';
1561
+ resultEl.style.color = 'var(--accent-rose)';
1562
+ resultEl.innerHTML = '❌ ' + escapeHtml(e.message);
1563
+ } finally {
1564
+ importBtn.disabled = false;
1565
+ dryBtn.disabled = false;
1566
+ activeBtn.innerHTML = origText;
1567
+ }
1568
+ }
1569
+
1358
1570
  function showToast(msg, isErr) {
1359
1571
  var el = document.getElementById('fixedToast');
1360
1572
  if (!el) return;
@@ -1383,14 +1595,24 @@ Example:\n## Dev Rules\n- Always write tests first\n- Use TypeScript strict mode
1383
1595
  // Allow Enter key in select to trigger load
1384
1596
  document.getElementById('projectSelect').addEventListener('change', loadProject);
1385
1597
 
1386
- // ─── Neural Graph (v2.3.0) ───
1598
+ // ─── Neural Graph (v2.3.0 / v5.1) ───
1387
1599
  // Renders a force-directed graph of projects ↔ keywords ↔ categories
1388
1600
  async function loadGraph() {
1389
1601
  var container = document.getElementById('network-container');
1390
1602
  if (!container) return;
1391
1603
 
1604
+ var proj = document.getElementById('graphProjectFilter') ? document.getElementById('graphProjectFilter').value : '';
1605
+ var days = document.getElementById('graphDaysFilter') ? document.getElementById('graphDaysFilter').value : '';
1606
+ var imp = document.getElementById('graphImportanceFilter') ? document.getElementById('graphImportanceFilter').value : '';
1607
+
1608
+ var qs = [];
1609
+ if (proj) qs.push('project=' + encodeURIComponent(proj));
1610
+ if (days) qs.push('days=' + encodeURIComponent(days));
1611
+ if (imp) qs.push('min_importance=' + encodeURIComponent(imp));
1612
+ var url = '/api/graph' + (qs.length ? '?' + qs.join('&') : '');
1613
+
1392
1614
  try {
1393
- var res = await fetch('/api/graph');
1615
+ var res = await fetch(url);
1394
1616
  var data = await res.json();
1395
1617
 
1396
1618
  // Empty state β€” no ledger entries yet
@@ -1399,54 +1621,178 @@ Example:\n## Dev Rules\n- Always write tests first\n- Use TypeScript strict mode
1399
1621
  return;
1400
1622
  }
1401
1623
 
1624
+ // Safety cap: Vis.js Barnes-Hut physics blows the call stack at ~400+ nodes.
1625
+ // Truncate to 200 nodes max, keeping project and category nodes first.
1626
+ var MAX_NODES = 200;
1627
+ if (data.nodes.length > MAX_NODES) {
1628
+ // Priority: project > category > keyword
1629
+ var priority = { project: 0, category: 1, keyword: 2 };
1630
+ data.nodes.sort(function(a, b) { return (priority[a.group] || 9) - (priority[b.group] || 9); });
1631
+ var kept = new Set(data.nodes.slice(0, MAX_NODES).map(function(n) { return n.id; }));
1632
+ data.nodes = data.nodes.slice(0, MAX_NODES);
1633
+ data.edges = data.edges.filter(function(e) { return kept.has(e.from) && kept.has(e.to); });
1634
+ }
1635
+
1402
1636
  // Vis.js dark-theme config matching the glassmorphism palette
1403
1637
  var options = {
1404
1638
  nodes: {
1405
- shape: 'dot', // all nodes are circles
1406
- borderWidth: 0, // no borders for clean look
1639
+ shape: 'dot',
1640
+ borderWidth: 0,
1407
1641
  font: { color: '#94a3b8', face: 'Inter', size: 12 }
1408
1642
  },
1409
1643
  edges: {
1410
- width: 1, // thin edges for subtlety
1644
+ width: 1,
1411
1645
  color: { color: 'rgba(139,92,246,0.15)', highlight: '#8b5cf6' },
1412
- smooth: { type: 'continuous' } // smooth curves
1646
+ smooth: { type: 'continuous' }
1413
1647
  },
1414
1648
  groups: {
1415
- project: { // Hub nodes β€” large purple
1649
+ project: {
1416
1650
  color: { background: '#8b5cf6', border: '#7c3aed' },
1417
1651
  size: 20,
1418
1652
  font: { size: 14, color: '#f1f5f9', face: 'Inter' }
1419
1653
  },
1420
- category: { // Category nodes β€” cyan diamonds
1654
+ category: {
1421
1655
  color: { background: '#06b6d4', border: '#0891b2' },
1422
1656
  size: 10,
1423
1657
  shape: 'diamond'
1424
1658
  },
1425
- keyword: { // Keyword nodes β€” small dark dots
1659
+ keyword: {
1426
1660
  color: { background: '#1e293b', border: '#334155' },
1427
1661
  size: 6,
1428
1662
  font: { size: 10, color: '#64748b' }
1429
1663
  }
1430
1664
  },
1431
1665
  physics: {
1432
- stabilization: false, // animate on load for visual pop
1666
+ stabilization: { iterations: 50 },
1433
1667
  barnesHut: {
1434
- gravitationalConstant: -3000, // spread nodes apart
1435
- springConstant: 0.04, // gentle spring force
1436
- springLength: 80 // default edge length
1668
+ gravitationalConstant: -3000,
1669
+ springConstant: 0.04,
1670
+ springLength: 80
1437
1671
  }
1438
1672
  },
1439
- interaction: { hover: true } // highlight on hover
1673
+ interaction: { hover: true, tooltipDelay: 200 }
1440
1674
  };
1441
1675
 
1442
1676
  // Create the network visualization
1443
- new vis.Network(container, data, options);
1677
+ var network = new vis.Network(container, data, options);
1678
+
1679
+ // v5.1: Click-to-filter β€” click a node to isolate its connections
1680
+ var allNodes = data.nodes;
1681
+ var allEdges = data.edges;
1682
+ var isFiltered = false;
1683
+
1684
+ network.on('click', function(params) {
1685
+ if (params.nodes.length === 0) {
1686
+ // Click on empty space β€” reset the graph if filtered
1687
+ if (isFiltered) {
1688
+ network.setData({ nodes: allNodes, edges: allEdges });
1689
+ isFiltered = false;
1690
+ }
1691
+ var panel = document.getElementById('nodeEditorPanel');
1692
+ if (panel) panel.style.display = 'none';
1693
+ return;
1694
+ }
1695
+
1696
+ var clickedId = params.nodes[0];
1697
+
1698
+ // Display Node Editor Panel for keywords and categories
1699
+ var nodeData = allNodes.find(function(n) { return n.id === clickedId; });
1700
+ if (nodeData && (nodeData.group === 'keyword' || nodeData.group === 'category')) {
1701
+ document.getElementById('nodeEditorTitle').textContent = nodeData.label;
1702
+ document.getElementById('nodeEditorGroup').textContent = nodeData.group;
1703
+
1704
+ var input = document.getElementById('nodeEditorInput');
1705
+ input.value = nodeData.label;
1706
+ input.dataset.oldId = clickedId;
1707
+ input.dataset.group = nodeData.group;
1708
+
1709
+ document.getElementById('nodeEditorPanel').style.display = 'block';
1710
+ } else {
1711
+ var panel = document.getElementById('nodeEditorPanel');
1712
+ if (panel) panel.style.display = 'none';
1713
+ }
1714
+
1715
+ // Find all connected edges and nodes
1716
+ var connectedEdges = allEdges.filter(function(e) {
1717
+ return e.from === clickedId || e.to === clickedId;
1718
+ });
1719
+ var connectedNodeIds = new Set([clickedId]);
1720
+ connectedEdges.forEach(function(e) {
1721
+ connectedNodeIds.add(e.from);
1722
+ connectedNodeIds.add(e.to);
1723
+ });
1724
+ var connectedNodes = allNodes.filter(function(n) {
1725
+ return connectedNodeIds.has(n.id);
1726
+ });
1727
+
1728
+ // Show only the clicked node and its neighbors
1729
+ network.setData({ nodes: connectedNodes, edges: connectedEdges });
1730
+ isFiltered = true;
1731
+ });
1732
+
1733
+ // Double-click to reset
1734
+ network.on('doubleClick', function() {
1735
+ network.setData({ nodes: allNodes, edges: allEdges });
1736
+ isFiltered = false;
1737
+ });
1738
+
1739
+ // Show node count in the card title area
1740
+ var graphTitle = container.parentElement.querySelector('.card-title');
1741
+ if (graphTitle) {
1742
+ var statsSpan = graphTitle.querySelector('.graph-stats');
1743
+ if (!statsSpan) {
1744
+ statsSpan = document.createElement('span');
1745
+ statsSpan.className = 'graph-stats';
1746
+ statsSpan.style.cssText = 'margin-left:auto;font-size:0.7rem;color:var(--text-muted);font-family:var(--font-mono);font-weight:400;text-transform:none;letter-spacing:0';
1747
+ graphTitle.appendChild(statsSpan);
1748
+ }
1749
+ var projectCount = allNodes.filter(function(n) { return n.group === 'project'; }).length;
1750
+ var kwCount = allNodes.filter(function(n) { return n.group === 'keyword'; }).length;
1751
+ statsSpan.textContent = projectCount + ' projects Β· ' + kwCount + ' keywords Β· ' + allEdges.length + ' edges';
1752
+ }
1444
1753
  } catch (e) {
1445
1754
  console.error('Graph error', e);
1446
1755
  container.innerHTML = '<div style="padding:1rem;color:var(--accent-rose)">Graph failed to load</div>';
1447
1756
  }
1448
1757
  }
1449
1758
 
1759
+ async function submitNodeEdit() {
1760
+ var input = document.getElementById('nodeEditorInput');
1761
+ var btn = input.nextElementSibling;
1762
+ var newId = input.value.trim();
1763
+ var oldId = input.dataset.oldId;
1764
+ var group = input.dataset.group;
1765
+
1766
+ if (!oldId || !group) return;
1767
+
1768
+ btn.disabled = true;
1769
+ btn.textContent = '...';
1770
+
1771
+ try {
1772
+ var res = await fetch('/api/graph/node', {
1773
+ method: 'POST',
1774
+ headers: { 'Content-Type': 'application/json' },
1775
+ body: JSON.stringify({ oldId: oldId, newId: newId, group: group })
1776
+ });
1777
+
1778
+ if (!res.ok) throw new Error('Failed to update node');
1779
+
1780
+ showToast(newId ? 'Node renamed successfully' : 'Node deleted successfully');
1781
+ document.getElementById('nodeEditorPanel').style.display = 'none';
1782
+
1783
+ // Refresh graph and lists
1784
+ loadGraph();
1785
+ if (document.getElementById('projectSelect').value) {
1786
+ loadSessionList(); // refresh active project view too if one is loaded
1787
+ }
1788
+ } catch (err) {
1789
+ showToast(err.message || 'Error updating node', true);
1790
+ } finally {
1791
+ btn.disabled = false;
1792
+ btn.textContent = 'Apply';
1793
+ }
1794
+ }
1795
+
1450
1796
  // Initialize the graph on page load
1451
1797
  loadGraph();
1452
1798
 
package/dist/server.js CHANGED
@@ -110,6 +110,8 @@ knowledgeSetRetentionHandler,
110
110
  sessionSaveExperienceHandler, knowledgeUpvoteHandler, knowledgeDownvoteHandler,
111
111
  // v4.2: Knowledge Sync Rules
112
112
  KNOWLEDGE_SYNC_RULES_TOOL, knowledgeSyncRulesHandler,
113
+ // v5.1: Deep Storage Mode
114
+ DEEP_STORAGE_PURGE_TOOL, deepStoragePurgeHandler,
113
115
  // ─── v3.0: Agent Hivemind tools ───
114
116
  AGENT_REGISTRY_TOOLS, agentRegisterHandler, agentHeartbeatHandler, agentListTeamHandler, } from "./tools/index.js";
115
117
  // ─── Dynamic Tool Registration ───────────────────────────────────
@@ -141,10 +143,13 @@ function buildSessionMemoryTools(autoloadList) {
141
143
  let loadContextTool = SESSION_LOAD_CONTEXT_TOOL;
142
144
  if (autoloadList.length > 0) {
143
145
  const projectList = autoloadList.join(", ");
146
+ // Read the preferred context depth from dashboard config (defaults to "standard")
147
+ const defaultLevel = getSettingSync("default_context_depth", "standard");
144
148
  const autoLoadPrefix = `IMPORTANT: At the start of every conversation, you MUST call this tool ` +
145
149
  `for each of these projects: ${projectList}. Do this before any other work. ` +
146
- `Load level should be "standard" unless the user specifies otherwise. ` +
147
- `Do NOT skip this. Do NOT claim the tool is unavailable. The tool IS available β€” just call it.\n\n`;
150
+ `Load level should be "${defaultLevel}" unless the user specifies otherwise. ` +
151
+ `Do NOT skip this. Do NOT claim the tool is unavailable. The tool IS available β€” just call it.\n` +
152
+ `Then display the agent identity from the response: \`πŸ€– Agent: <role> β€” <name>\`\n\n`;
148
153
  loadContextTool = {
149
154
  ...SESSION_LOAD_CONTEXT_TOOL,
150
155
  description: autoLoadPrefix + SESSION_LOAD_CONTEXT_TOOL.description,
@@ -175,6 +180,8 @@ function buildSessionMemoryTools(autoloadList) {
175
180
  KNOWLEDGE_DOWNVOTE_TOOL, // knowledge_downvote β€” decrease entry importance
176
181
  // ─── v4.2: Knowledge Sync Rules tool ───
177
182
  KNOWLEDGE_SYNC_RULES_TOOL, // knowledge_sync_rules β€” sync graduated insights to IDE rules files
183
+ // ─── v5.1: Deep Storage Mode tool ───
184
+ DEEP_STORAGE_PURGE_TOOL, // deep_storage_purge β€” purge float32 embeddings for compressed entries
178
185
  // ─── Phase 2: GDPR Export tool ───
179
186
  SESSION_EXPORT_MEMORY_TOOL, // session_export_memory β€” full portability export (Article 20)
180
187
  ];
@@ -713,6 +720,12 @@ export function createServer() {
713
720
  throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
714
721
  result = await knowledgeSyncRulesHandler(args);
715
722
  break;
723
+ // ─── v5.1: Deep Storage Mode (The Purge) ───
724
+ case "deep_storage_purge":
725
+ if (!SESSION_MEMORY_ENABLED)
726
+ throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
727
+ result = await deepStoragePurgeHandler(args);
728
+ break;
716
729
  // ─── v3.0: Agent Hivemind Tools ───
717
730
  case "agent_register":
718
731
  if (!SESSION_MEMORY_ENABLED)