prism-mcp-server 2.1.2 β 2.3.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/README.md +30 -3
- package/dist/dashboard/server.js +84 -0
- package/dist/dashboard/ui.js +182 -2
- package/dist/server.js +15 -9
- package/dist/storage/sqlite.js +119 -0
- package/dist/storage/supabase.js +93 -0
- package/dist/tools/index.js +2 -2
- package/dist/tools/sessionMemoryDefinitions.js +33 -0
- package/dist/tools/sessionMemoryHandlers.js +192 -1
- package/dist/utils/factMerger.js +104 -0
- package/dist/utils/healthCheck.js +307 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,9 +14,26 @@
|
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
17
|
-
## What's New in v2.0
|
|
17
|
+
## What's New in v2.3.0 β AI Reasoning Engine π§
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
| Feature | Description |
|
|
20
|
+
|---|---|
|
|
21
|
+
| πΈοΈ **Neural Graph** | Interactive knowledge graph on the Mind Palace Dashboard β visualize how projects connect through shared keywords and categories using Vis.js force-directed layout. |
|
|
22
|
+
| π‘οΈ **Prompt Injection Shield** | Gemini-powered security scan in `session_health_check` β detects system override attempts, jailbreaks, and data exfiltration hidden in agent memory. Tuned to avoid false positives on normal dev commands. |
|
|
23
|
+
| 𧬠**Fact Merger** | Async LLM contradiction resolution on every handoff save β if old context says "Postgres" and new says "MySQL", Gemini silently merges the facts in the background. Zero latency impact (fire-and-forget). |
|
|
24
|
+
|
|
25
|
+
<details>
|
|
26
|
+
<summary><strong>What's in v2.2.0</strong></summary>
|
|
27
|
+
|
|
28
|
+
| Feature | Description |
|
|
29
|
+
|---|---|
|
|
30
|
+
| π©Ί **Brain Health Check** | `session_health_check` β like Unix `fsck` for your agent's memory. Detects missing embeddings, duplicate entries, orphaned handoffs, and stale rollups. Use `auto_fix: true` to repair automatically. |
|
|
31
|
+
| π **Mind Palace Health** | Brain health indicator on the Mind Palace Dashboard β see your memory integrity at a glance. |
|
|
32
|
+
|
|
33
|
+
</details>
|
|
34
|
+
|
|
35
|
+
<details>
|
|
36
|
+
<summary><strong>What's in v2.0 "Mind Palace"</strong></summary>
|
|
20
37
|
|
|
21
38
|
| Feature | Description |
|
|
22
39
|
|---|---|
|
|
@@ -29,6 +46,8 @@ Prism MCP has been completely rebuilt from the ground up to support **local-firs
|
|
|
29
46
|
| π **Code Mode Templates** | 8 pre-built QuickJS extraction templates for GitHub, Jira, OpenAPI, Slack, CSV, and DOM parsing β zero reasoning tokens. |
|
|
30
47
|
| π **Reality Drift Detection** | Prism captures Git state on save and warns if files changed outside the agent's view. |
|
|
31
48
|
|
|
49
|
+
</details>
|
|
50
|
+
|
|
32
51
|
---
|
|
33
52
|
|
|
34
53
|
## Quick Start (Zero Config β Local Mode)
|
|
@@ -301,7 +320,7 @@ graph TB
|
|
|
301
320
|
| `knowledge_search` | Semantic search across accumulated knowledge |
|
|
302
321
|
| `knowledge_forget` | Prune outdated or incorrect memories (4 modes + dry_run) |
|
|
303
322
|
| `session_search_memory` | Vector similarity search across all sessions |
|
|
304
|
-
| `
|
|
323
|
+
| `session_compact_ledger` | Auto-compact old ledger entries via Gemini-powered summarization |
|
|
305
324
|
|
|
306
325
|
### v2.0 Advanced Memory Tools
|
|
307
326
|
|
|
@@ -312,6 +331,12 @@ graph TB
|
|
|
312
331
|
| `session_save_image` | Save a screenshot/image to the visual memory vault |
|
|
313
332
|
| `session_view_image` | Retrieve and display a saved image from the vault |
|
|
314
333
|
|
|
334
|
+
### v2.2 Brain Health Tools
|
|
335
|
+
|
|
336
|
+
| Tool | Purpose | Key Args | Returns |
|
|
337
|
+
|------|---------|----------|---------|
|
|
338
|
+
| `session_health_check` | Scan brain for integrity issues (`fsck`) | `auto_fix` (boolean) | Health report & auto-repairs |
|
|
339
|
+
|
|
315
340
|
### Code Mode Templates (v2.1)
|
|
316
341
|
|
|
317
342
|
Instead of writing custom JavaScript, pass a `template` name for instant extraction:
|
|
@@ -560,6 +585,8 @@ See [`vertex-ai/`](vertex-ai/) for setup and benchmarks.
|
|
|
560
585
|
β βββ googleAi.ts # Gemini SDK wrapper
|
|
561
586
|
β βββ executor.ts # QuickJS sandbox executor
|
|
562
587
|
β βββ autoCapture.ts # Dev server HTML snapshot utility
|
|
588
|
+
β βββ healthCheck.ts # Brain integrity engine (v2.2.0) + security scanner (v2.3.0)
|
|
589
|
+
β βββ factMerger.ts # Async LLM contradiction resolution (v2.3.0)
|
|
563
590
|
β βββ git.ts # Git state capture + drift detection
|
|
564
591
|
β βββ embeddingApi.ts # Embedding generation (Gemini)
|
|
565
592
|
β βββ keywordExtractor.ts # Zero-dependency NLP keyword extraction
|
package/dist/dashboard/server.js
CHANGED
|
@@ -68,6 +68,90 @@ export async function startDashboardServer() {
|
|
|
68
68
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
69
69
|
return res.end(JSON.stringify({ context, ledger, history }));
|
|
70
70
|
}
|
|
71
|
+
// βββ API: Brain Health Check (v2.2.0) βββ
|
|
72
|
+
if (url.pathname === "/api/health") {
|
|
73
|
+
try {
|
|
74
|
+
const { runHealthCheck } = await import("../utils/healthCheck.js");
|
|
75
|
+
const stats = await storage.getHealthStats(PRISM_USER_ID);
|
|
76
|
+
const report = runHealthCheck(stats);
|
|
77
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
78
|
+
return res.end(JSON.stringify(report));
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
console.error("[Dashboard] Health check error:", err);
|
|
82
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
83
|
+
return res.end(JSON.stringify({
|
|
84
|
+
status: "unknown",
|
|
85
|
+
summary: "Health check unavailable",
|
|
86
|
+
issues: [],
|
|
87
|
+
counts: { errors: 0, warnings: 0, infos: 0 },
|
|
88
|
+
totals: { activeEntries: 0, handoffs: 0, rollups: 0 },
|
|
89
|
+
timestamp: new Date().toISOString(),
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// βββ API: Knowledge Graph Data (v2.3.0) βββ
|
|
94
|
+
if (url.pathname === "/api/graph") {
|
|
95
|
+
// Fetch recent ledger entries to build the graph
|
|
96
|
+
// We look at the last 100 entries to keep the graph relevant but performant
|
|
97
|
+
const entries = await storage.getLedgerEntries({
|
|
98
|
+
limit: "100",
|
|
99
|
+
order: "created_at.desc",
|
|
100
|
+
select: "project,keywords",
|
|
101
|
+
});
|
|
102
|
+
// Deduplication sets for nodes and edges
|
|
103
|
+
const nodes = [];
|
|
104
|
+
const edges = [];
|
|
105
|
+
const nodeIds = new Set(); // track unique node IDs
|
|
106
|
+
const edgeIds = new Set(); // track unique edges
|
|
107
|
+
// Helper: add a node only if it doesn't already exist
|
|
108
|
+
const addNode = (id, group, label) => {
|
|
109
|
+
if (!nodeIds.has(id)) {
|
|
110
|
+
nodes.push({ id, label: label || id, group });
|
|
111
|
+
nodeIds.add(id);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
// Helper: add an edge only if it doesn't already exist
|
|
115
|
+
const addEdge = (from, to) => {
|
|
116
|
+
const id = `${from}-${to}`; // deterministic edge ID
|
|
117
|
+
if (!edgeIds.has(id)) {
|
|
118
|
+
edges.push({ from, to });
|
|
119
|
+
edgeIds.add(id);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
// Transform relational data into graph nodes & edges
|
|
123
|
+
entries.forEach(row => {
|
|
124
|
+
if (!row.project)
|
|
125
|
+
return; // skip rows without project
|
|
126
|
+
// 1. Project node (hub β large purple dot)
|
|
127
|
+
addNode(row.project, "project");
|
|
128
|
+
// 2. Keyword nodes (spokes β small dots)
|
|
129
|
+
let keywords = [];
|
|
130
|
+
// Handle SQLite (JSON string) vs Supabase (native array)
|
|
131
|
+
if (Array.isArray(row.keywords)) {
|
|
132
|
+
keywords = row.keywords;
|
|
133
|
+
}
|
|
134
|
+
else if (typeof row.keywords === "string") {
|
|
135
|
+
try {
|
|
136
|
+
keywords = JSON.parse(row.keywords);
|
|
137
|
+
}
|
|
138
|
+
catch { /* skip malformed */ }
|
|
139
|
+
}
|
|
140
|
+
// Create nodes + edges for each keyword
|
|
141
|
+
keywords.forEach((kw) => {
|
|
142
|
+
if (kw.length < 3)
|
|
143
|
+
return; // skip noise like "a", "is"
|
|
144
|
+
// Handle categories (cat:debugging) vs raw keywords
|
|
145
|
+
const isCat = kw.startsWith("cat:");
|
|
146
|
+
const group = isCat ? "category" : "keyword";
|
|
147
|
+
const label = isCat ? kw.replace("cat:", "") : kw;
|
|
148
|
+
addNode(kw, group, label); // keyword/category node
|
|
149
|
+
addEdge(row.project, kw); // edge: project β keyword
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
153
|
+
return res.end(JSON.stringify({ nodes, edges }));
|
|
154
|
+
}
|
|
71
155
|
// βββ 404 βββ
|
|
72
156
|
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
73
157
|
res.end("Not found");
|
package/dist/dashboard/ui.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Mind Palace Dashboard β UI Renderer (v2.0
|
|
2
|
+
* Mind Palace Dashboard β UI Renderer (v2.2.0)
|
|
3
3
|
*
|
|
4
4
|
* Pure CSS + Vanilla JS single-page dashboard.
|
|
5
5
|
* No build step, no Tailwind, no framework β served as a template literal.
|
|
@@ -22,6 +22,8 @@ export function renderDashboardHTML() {
|
|
|
22
22
|
<title>Prism MCP β Mind Palace</title>
|
|
23
23
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
24
24
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
25
|
+
<!-- Vis.js for Neural Graph (v2.3.0) -->
|
|
26
|
+
<script type="text/javascript" src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
|
|
25
27
|
<style>
|
|
26
28
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
27
29
|
|
|
@@ -224,6 +226,50 @@ export function renderDashboardHTML() {
|
|
|
224
226
|
/* βββ Fade in animation βββ */
|
|
225
227
|
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
|
|
226
228
|
.fade-in { animation: fadeIn 0.4s ease-out forwards; }
|
|
229
|
+
|
|
230
|
+
/* βββ Brain Health Indicator (v2.2.0) βββ */
|
|
231
|
+
.health-status {
|
|
232
|
+
display: flex; align-items: center; gap: 0.75rem;
|
|
233
|
+
padding: 0.75rem 1rem; border-radius: var(--radius-sm);
|
|
234
|
+
background: rgba(15,23,42,0.6); margin-bottom: 1rem;
|
|
235
|
+
}
|
|
236
|
+
.health-dot {
|
|
237
|
+
width: 12px; height: 12px; border-radius: 50%;
|
|
238
|
+
flex-shrink: 0; position: relative;
|
|
239
|
+
}
|
|
240
|
+
.health-dot::after {
|
|
241
|
+
content: ''; position: absolute; inset: -3px;
|
|
242
|
+
border-radius: 50%; animation: healthPulse 2s ease-in-out infinite;
|
|
243
|
+
}
|
|
244
|
+
.health-dot.healthy { background: var(--accent-green); }
|
|
245
|
+
.health-dot.healthy::after { border: 2px solid rgba(16,185,129,0.3); }
|
|
246
|
+
.health-dot.degraded { background: var(--accent-amber); }
|
|
247
|
+
.health-dot.degraded::after { border: 2px solid rgba(245,158,11,0.3); }
|
|
248
|
+
.health-dot.unhealthy { background: var(--accent-rose); }
|
|
249
|
+
.health-dot.unhealthy::after { border: 2px solid rgba(244,63,94,0.3); }
|
|
250
|
+
.health-dot.unknown { background: var(--text-muted); }
|
|
251
|
+
.health-dot.unknown::after { border: 2px solid rgba(100,116,139,0.3); }
|
|
252
|
+
@keyframes healthPulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }
|
|
253
|
+
.health-label { font-size: 0.8rem; font-weight: 500; }
|
|
254
|
+
.health-summary { font-size: 0.75rem; color: var(--text-muted); }
|
|
255
|
+
.health-issues { font-size: 0.8rem; color: var(--text-secondary); margin-top: 0.5rem; }
|
|
256
|
+
.health-issues .issue-row {
|
|
257
|
+
padding: 0.3rem 0; display: flex; gap: 0.5rem; align-items: flex-start;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/* βββ Neural Graph (v2.3.0) βββ */
|
|
261
|
+
#network-container {
|
|
262
|
+
width: 100%; height: 300px;
|
|
263
|
+
border-radius: var(--radius);
|
|
264
|
+
background: rgba(0,0,0,0.2);
|
|
265
|
+
border: 1px solid var(--border-glass);
|
|
266
|
+
}
|
|
267
|
+
.refresh-btn {
|
|
268
|
+
margin-left: auto; background: none; border: none;
|
|
269
|
+
color: var(--text-muted); cursor: pointer; font-size: 0.85rem;
|
|
270
|
+
transition: color 0.2s;
|
|
271
|
+
}
|
|
272
|
+
.refresh-btn:hover { color: var(--accent-purple); }
|
|
227
273
|
</style>
|
|
228
274
|
</head>
|
|
229
275
|
<body>
|
|
@@ -233,7 +279,7 @@ export function renderDashboardHTML() {
|
|
|
233
279
|
<div class="logo">
|
|
234
280
|
<span class="logo-icon">π§ </span>
|
|
235
281
|
Prism Mind Palace
|
|
236
|
-
<span class="version-badge">v2.0</span>
|
|
282
|
+
<span class="version-badge">v2.2.0</span>
|
|
237
283
|
</div>
|
|
238
284
|
<div class="selector">
|
|
239
285
|
<select id="projectSelect">
|
|
@@ -271,6 +317,19 @@ export function renderDashboardHTML() {
|
|
|
271
317
|
<div class="git-row"><span class="git-label">Key Context</span><span class="git-value" id="keyContext" style="font-family:var(--font-sans);max-width:200px;text-align:right">β</span></div>
|
|
272
318
|
</div>
|
|
273
319
|
|
|
320
|
+
<!-- Brain Health (v2.2.0) -->
|
|
321
|
+
<div class="card" id="healthCard" style="display:none">
|
|
322
|
+
<div class="card-title"><span class="dot" style="background:var(--accent-green)"></span> Brain Health π©Ί</div>
|
|
323
|
+
<div class="health-status">
|
|
324
|
+
<div class="health-dot unknown" id="healthDot"></div>
|
|
325
|
+
<div>
|
|
326
|
+
<div class="health-label" id="healthLabel">Scanning...</div>
|
|
327
|
+
<div class="health-summary" id="healthSummary"></div>
|
|
328
|
+
</div>
|
|
329
|
+
</div>
|
|
330
|
+
<div class="health-issues" id="healthIssues"></div>
|
|
331
|
+
</div>
|
|
332
|
+
|
|
274
333
|
<!-- Morning Briefing -->
|
|
275
334
|
<div class="card" id="briefingCard" style="display:none">
|
|
276
335
|
<div class="card-title"><span class="dot" style="background:var(--accent-amber)"></span> Morning Briefing π
</div>
|
|
@@ -286,6 +345,17 @@ export function renderDashboardHTML() {
|
|
|
286
345
|
|
|
287
346
|
<!-- Right Column -->
|
|
288
347
|
<div class="grid" style="align-content: start;">
|
|
348
|
+
|
|
349
|
+
<!-- Neural Graph (v2.3.0) -->
|
|
350
|
+
<div class="card">
|
|
351
|
+
<div class="card-title">
|
|
352
|
+
<span class="dot" style="background:var(--accent-blue)"></span>
|
|
353
|
+
Neural Graph πΈοΈ
|
|
354
|
+
<button onclick="loadGraph()" class="refresh-btn">β»</button>
|
|
355
|
+
</div>
|
|
356
|
+
<div id="network-container">Loading nodes...</div>
|
|
357
|
+
</div>
|
|
358
|
+
|
|
289
359
|
<!-- Time Travel -->
|
|
290
360
|
<div class="card">
|
|
291
361
|
<div class="card-title"><span class="dot" style="background:var(--accent-purple)"></span> Time Travel History π°οΈ</div>
|
|
@@ -413,6 +483,49 @@ export function renderDashboardHTML() {
|
|
|
413
483
|
ledgerEl.innerHTML = '<div style="color:var(--text-muted);font-size:0.85rem;padding:1rem;text-align:center">No ledger entries yet.</div>';
|
|
414
484
|
}
|
|
415
485
|
|
|
486
|
+
// βββ Brain Health (v2.2.0) βββ
|
|
487
|
+
try {
|
|
488
|
+
var healthRes = await fetch('/api/health');
|
|
489
|
+
var healthData = await healthRes.json();
|
|
490
|
+
var healthCard = document.getElementById('healthCard');
|
|
491
|
+
var healthDot = document.getElementById('healthDot');
|
|
492
|
+
var healthLabel = document.getElementById('healthLabel');
|
|
493
|
+
var healthSummary = document.getElementById('healthSummary');
|
|
494
|
+
var healthIssues = document.getElementById('healthIssues');
|
|
495
|
+
|
|
496
|
+
// Set the dot color based on status
|
|
497
|
+
healthDot.className = 'health-dot ' + (healthData.status || 'unknown');
|
|
498
|
+
|
|
499
|
+
// Map status to emoji + label
|
|
500
|
+
var statusMap = { healthy: 'β
Healthy', degraded: 'β οΈ Degraded', unhealthy: 'π΄ Unhealthy' };
|
|
501
|
+
healthLabel.textContent = statusMap[healthData.status] || 'β Unknown';
|
|
502
|
+
|
|
503
|
+
// Stats summary line
|
|
504
|
+
var t = healthData.totals || {};
|
|
505
|
+
healthSummary.textContent = (t.activeEntries || 0) + ' entries Β· ' +
|
|
506
|
+
(t.handoffs || 0) + ' handoffs Β· ' +
|
|
507
|
+
(t.rollups || 0) + ' rollups';
|
|
508
|
+
|
|
509
|
+
// Issue rows
|
|
510
|
+
var issues = healthData.issues || [];
|
|
511
|
+
if (issues.length > 0) {
|
|
512
|
+
var sevIcons = { error: 'π΄', warning: 'π‘', info: 'π΅' };
|
|
513
|
+
healthIssues.innerHTML = issues.map(function(i) {
|
|
514
|
+
return '<div class="issue-row">' +
|
|
515
|
+
'<span>' + (sevIcons[i.severity] || 'β') + '</span>' +
|
|
516
|
+
'<span>' + escapeHtml(i.message) + '</span>' +
|
|
517
|
+
'</div>';
|
|
518
|
+
}).join('');
|
|
519
|
+
} else {
|
|
520
|
+
healthIssues.innerHTML = '<div style="color:var(--accent-green);font-size:0.8rem">π No issues found</div>';
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
healthCard.style.display = 'block';
|
|
524
|
+
} catch(he) {
|
|
525
|
+
// Health check not available β silently skip
|
|
526
|
+
console.warn('Health check unavailable:', he);
|
|
527
|
+
}
|
|
528
|
+
|
|
416
529
|
document.getElementById('content').className = 'grid grid-main fade-in';
|
|
417
530
|
document.getElementById('content').style.display = 'grid';
|
|
418
531
|
} catch(e) {
|
|
@@ -438,6 +551,73 @@ export function renderDashboardHTML() {
|
|
|
438
551
|
|
|
439
552
|
// Allow Enter key in select to trigger load
|
|
440
553
|
document.getElementById('projectSelect').addEventListener('change', loadProject);
|
|
554
|
+
|
|
555
|
+
// βββ Neural Graph (v2.3.0) βββ
|
|
556
|
+
// Renders a force-directed graph of projects β keywords β categories
|
|
557
|
+
async function loadGraph() {
|
|
558
|
+
var container = document.getElementById('network-container');
|
|
559
|
+
if (!container) return;
|
|
560
|
+
|
|
561
|
+
try {
|
|
562
|
+
var res = await fetch('/api/graph');
|
|
563
|
+
var data = await res.json();
|
|
564
|
+
|
|
565
|
+
// Empty state β no ledger entries yet
|
|
566
|
+
if (data.nodes.length === 0) {
|
|
567
|
+
container.innerHTML = '<div style="display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-muted);font-size:0.85rem">No knowledge associations found yet.</div>';
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Vis.js dark-theme config matching the glassmorphism palette
|
|
572
|
+
var options = {
|
|
573
|
+
nodes: {
|
|
574
|
+
shape: 'dot', // all nodes are circles
|
|
575
|
+
borderWidth: 0, // no borders for clean look
|
|
576
|
+
font: { color: '#94a3b8', face: 'Inter', size: 12 }
|
|
577
|
+
},
|
|
578
|
+
edges: {
|
|
579
|
+
width: 1, // thin edges for subtlety
|
|
580
|
+
color: { color: 'rgba(139,92,246,0.15)', highlight: '#8b5cf6' },
|
|
581
|
+
smooth: { type: 'continuous' } // smooth curves
|
|
582
|
+
},
|
|
583
|
+
groups: {
|
|
584
|
+
project: { // Hub nodes β large purple
|
|
585
|
+
color: { background: '#8b5cf6', border: '#7c3aed' },
|
|
586
|
+
size: 20,
|
|
587
|
+
font: { size: 14, color: '#f1f5f9', face: 'Inter' }
|
|
588
|
+
},
|
|
589
|
+
category: { // Category nodes β cyan diamonds
|
|
590
|
+
color: { background: '#06b6d4', border: '#0891b2' },
|
|
591
|
+
size: 10,
|
|
592
|
+
shape: 'diamond'
|
|
593
|
+
},
|
|
594
|
+
keyword: { // Keyword nodes β small dark dots
|
|
595
|
+
color: { background: '#1e293b', border: '#334155' },
|
|
596
|
+
size: 6,
|
|
597
|
+
font: { size: 10, color: '#64748b' }
|
|
598
|
+
}
|
|
599
|
+
},
|
|
600
|
+
physics: {
|
|
601
|
+
stabilization: false, // animate on load for visual pop
|
|
602
|
+
barnesHut: {
|
|
603
|
+
gravitationalConstant: -3000, // spread nodes apart
|
|
604
|
+
springConstant: 0.04, // gentle spring force
|
|
605
|
+
springLength: 80 // default edge length
|
|
606
|
+
}
|
|
607
|
+
},
|
|
608
|
+
interaction: { hover: true } // highlight on hover
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
// Create the network visualization
|
|
612
|
+
new vis.Network(container, data, options);
|
|
613
|
+
} catch (e) {
|
|
614
|
+
console.error('Graph error', e);
|
|
615
|
+
container.innerHTML = '<div style="padding:1rem;color:var(--accent-rose)">Graph failed to load</div>';
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// Initialize the graph on page load
|
|
620
|
+
loadGraph();
|
|
441
621
|
</script>
|
|
442
622
|
</body>
|
|
443
623
|
</html>`;
|
package/dist/server.js
CHANGED
|
@@ -72,17 +72,21 @@ import { WEB_SEARCH_TOOL, BRAVE_WEB_SEARCH_CODE_MODE_TOOL, LOCAL_SEARCH_TOOL, BR
|
|
|
72
72
|
// Session memory tools β only used if Supabase is configured
|
|
73
73
|
import { SESSION_SAVE_LEDGER_TOOL, SESSION_SAVE_HANDOFF_TOOL, SESSION_LOAD_CONTEXT_TOOL, KNOWLEDGE_SEARCH_TOOL, KNOWLEDGE_FORGET_TOOL,
|
|
74
74
|
// βββ v0.4.0: New tool definitions (Enhancements #2 and #4) βββ
|
|
75
|
-
SESSION_COMPACT_LEDGER_TOOL, SESSION_SEARCH_MEMORY_TOOL,
|
|
75
|
+
SESSION_COMPACT_LEDGER_TOOL, SESSION_SEARCH_MEMORY_TOOL,
|
|
76
76
|
// βββ v2.0: Time Travel tool definitions βββ
|
|
77
77
|
MEMORY_HISTORY_TOOL, MEMORY_CHECKOUT_TOOL,
|
|
78
78
|
// βββ v2.0: Visual Memory tool definitions βββ
|
|
79
|
-
SESSION_SAVE_IMAGE_TOOL, SESSION_VIEW_IMAGE_TOOL,
|
|
79
|
+
SESSION_SAVE_IMAGE_TOOL, SESSION_VIEW_IMAGE_TOOL,
|
|
80
|
+
// βββ v2.2.0: Health Check tool definition βββ
|
|
81
|
+
SESSION_HEALTH_CHECK_TOOL, sessionSaveLedgerHandler, sessionSaveHandoffHandler, sessionLoadContextHandler, knowledgeSearchHandler, knowledgeForgetHandler,
|
|
80
82
|
// βββ v0.4.0: New tool handlers βββ
|
|
81
|
-
compactLedgerHandler, sessionSearchMemoryHandler,
|
|
83
|
+
compactLedgerHandler, sessionSearchMemoryHandler,
|
|
82
84
|
// βββ v2.0: Time Travel handlers βββ
|
|
83
85
|
memoryHistoryHandler, memoryCheckoutHandler,
|
|
84
86
|
// βββ v2.0: Visual Memory handlers βββ
|
|
85
|
-
sessionSaveImageHandler, sessionViewImageHandler,
|
|
87
|
+
sessionSaveImageHandler, sessionViewImageHandler,
|
|
88
|
+
// βββ v2.2.0: Health Check handler βββ
|
|
89
|
+
sessionHealthCheckHandler, } from "./tools/index.js";
|
|
86
90
|
// βββ Dynamic Tool Registration βββββββββββββββββββββββββββββββββββ
|
|
87
91
|
// Base tools: always available regardless of configuration
|
|
88
92
|
const BASE_TOOLS = [
|
|
@@ -106,12 +110,13 @@ const SESSION_MEMORY_TOOLS = [
|
|
|
106
110
|
KNOWLEDGE_FORGET_TOOL, // knowledge_forget β prune bad/old memories
|
|
107
111
|
SESSION_COMPACT_LEDGER_TOOL, // session_compact_ledger β auto-compact old ledger entries (v0.4.0)
|
|
108
112
|
SESSION_SEARCH_MEMORY_TOOL, // session_search_memory β semantic search via embeddings (v0.4.0)
|
|
109
|
-
SESSION_BACKFILL_EMBEDDINGS_TOOL, // session_backfill_embeddings β repair missing embeddings
|
|
110
113
|
MEMORY_HISTORY_TOOL, // memory_history β view version timeline (v2.0)
|
|
111
114
|
MEMORY_CHECKOUT_TOOL, // memory_checkout β revert to past version (v2.0)
|
|
112
115
|
// βββ v2.0: Visual Memory tools βββ
|
|
113
116
|
SESSION_SAVE_IMAGE_TOOL, // session_save_image β save image to media vault (v2.0)
|
|
114
117
|
SESSION_VIEW_IMAGE_TOOL, // session_view_image β retrieve image from vault (v2.0)
|
|
118
|
+
// βββ v2.2.0: Health Check tool βββ
|
|
119
|
+
SESSION_HEALTH_CHECK_TOOL, // session_health_check β brain integrity checker (v2.2.0)
|
|
115
120
|
];
|
|
116
121
|
// Combine: if session memory is enabled, add those tools too
|
|
117
122
|
const ALL_TOOLS = [
|
|
@@ -477,10 +482,6 @@ export function createServer() {
|
|
|
477
482
|
if (!SESSION_MEMORY_ENABLED)
|
|
478
483
|
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
479
484
|
return await sessionSearchMemoryHandler(args);
|
|
480
|
-
case "session_backfill_embeddings":
|
|
481
|
-
if (!SESSION_MEMORY_ENABLED)
|
|
482
|
-
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
483
|
-
return await backfillEmbeddingsHandler(args);
|
|
484
485
|
// βββ v2.0: Time Travel Tools βββ
|
|
485
486
|
case "memory_history":
|
|
486
487
|
if (!SESSION_MEMORY_ENABLED)
|
|
@@ -499,6 +500,11 @@ export function createServer() {
|
|
|
499
500
|
if (!SESSION_MEMORY_ENABLED)
|
|
500
501
|
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
501
502
|
return await sessionViewImageHandler(args);
|
|
503
|
+
// βββ v2.2.0: Health Check Tool βββ
|
|
504
|
+
case "session_health_check":
|
|
505
|
+
if (!SESSION_MEMORY_ENABLED)
|
|
506
|
+
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
507
|
+
return await sessionHealthCheckHandler(args);
|
|
502
508
|
default:
|
|
503
509
|
return {
|
|
504
510
|
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
package/dist/storage/sqlite.js
CHANGED
|
@@ -726,4 +726,123 @@ export class SqliteStorage {
|
|
|
726
726
|
const result = await this.db.execute("SELECT DISTINCT project FROM session_handoffs ORDER BY project ASC");
|
|
727
727
|
return result.rows.map(row => row.project);
|
|
728
728
|
}
|
|
729
|
+
// βββ v2.2.0 Health Check (fsck) βββββββββββββββββββββββββββββ
|
|
730
|
+
/**
|
|
731
|
+
* Gather raw health statistics for the integrity checker.
|
|
732
|
+
*
|
|
733
|
+
* This method runs 5 lightweight SQL queries and returns raw data.
|
|
734
|
+
* The heavy analysis (duplicate detection via Jaccard similarity)
|
|
735
|
+
* happens in healthCheck.ts in pure JS β keeping SQLite free of
|
|
736
|
+
* C-extension dependencies like Levenshtein.
|
|
737
|
+
*/
|
|
738
|
+
async getHealthStats(userId) {
|
|
739
|
+
// ββ Check 1: Count entries with no embedding vector ββββββββββ
|
|
740
|
+
// When Gemini API is down during save, the fire-and-forget
|
|
741
|
+
// embedding call fails silently. These rows need backfill.
|
|
742
|
+
const missingResult = await this.db.execute({
|
|
743
|
+
sql: `
|
|
744
|
+
SELECT COUNT(*) as cnt
|
|
745
|
+
FROM session_ledger
|
|
746
|
+
WHERE user_id = ?
|
|
747
|
+
AND archived_at IS NULL
|
|
748
|
+
AND embedding IS NULL
|
|
749
|
+
`,
|
|
750
|
+
args: [userId], // bind user_id to the ? placeholder
|
|
751
|
+
});
|
|
752
|
+
const missingEmbeddings = Number(// extract count, default 0
|
|
753
|
+
missingResult.rows[0]?.cnt ?? 0);
|
|
754
|
+
// ββ Check 2: Fetch active summaries for JS duplicate detection β
|
|
755
|
+
// We pull id + project + summary into memory so healthCheck.ts
|
|
756
|
+
// can run Jaccard similarity in pure JS (~5ms for typical sets).
|
|
757
|
+
// The Compactor keeps the active ledger small, so this is safe.
|
|
758
|
+
const summariesResult = await this.db.execute({
|
|
759
|
+
sql: `
|
|
760
|
+
SELECT id, project, summary
|
|
761
|
+
FROM session_ledger
|
|
762
|
+
WHERE user_id = ?
|
|
763
|
+
AND archived_at IS NULL
|
|
764
|
+
`,
|
|
765
|
+
args: [userId], // bind user_id to the ? placeholder
|
|
766
|
+
});
|
|
767
|
+
// Map raw DB rows to typed objects for the health engine
|
|
768
|
+
const activeLedgerSummaries = summariesResult.rows.map(row => ({
|
|
769
|
+
id: row.id, // unique entry identifier
|
|
770
|
+
project: row.project, // project this entry belongs to
|
|
771
|
+
summary: row.summary, // text we compare for duplicates
|
|
772
|
+
}));
|
|
773
|
+
// ββ Check 3: Find orphaned handoffs ββββββββββββββββββββββββββ
|
|
774
|
+
// An orphaned handoff = handoff state exists but zero active
|
|
775
|
+
// ledger entries back it. Usually from testing or bugs.
|
|
776
|
+
// LEFT JOIN + HAVING COUNT = 0 finds projects with no entries.
|
|
777
|
+
const orphanResult = await this.db.execute({
|
|
778
|
+
sql: `
|
|
779
|
+
SELECT h.project
|
|
780
|
+
FROM session_handoffs h
|
|
781
|
+
LEFT JOIN session_ledger l
|
|
782
|
+
ON h.project = l.project
|
|
783
|
+
AND h.user_id = l.user_id
|
|
784
|
+
AND l.archived_at IS NULL
|
|
785
|
+
WHERE h.user_id = ?
|
|
786
|
+
GROUP BY h.project
|
|
787
|
+
HAVING COUNT(l.id) = 0
|
|
788
|
+
`,
|
|
789
|
+
args: [userId], // bind user_id to the ? placeholder
|
|
790
|
+
});
|
|
791
|
+
// Map to simple project name objects
|
|
792
|
+
const orphanedHandoffs = orphanResult.rows.map(row => ({
|
|
793
|
+
project: row.project, // the orphaned project name
|
|
794
|
+
}));
|
|
795
|
+
// ββ Check 4: Count stale rollups βββββββββββββββββββββββββββββ
|
|
796
|
+
// A rollup entry should have archived originals backing it.
|
|
797
|
+
// If those originals were hard-deleted, the rollup is stale.
|
|
798
|
+
// Self-join: rollups (is_rollup=1) LEFT JOIN archived entries.
|
|
799
|
+
const staleResult = await this.db.execute({
|
|
800
|
+
sql: `
|
|
801
|
+
SELECT r.id
|
|
802
|
+
FROM session_ledger r
|
|
803
|
+
LEFT JOIN session_ledger a
|
|
804
|
+
ON a.archived_at IS NOT NULL
|
|
805
|
+
AND a.project = r.project
|
|
806
|
+
AND a.user_id = r.user_id
|
|
807
|
+
WHERE r.user_id = ?
|
|
808
|
+
AND r.is_rollup = 1
|
|
809
|
+
AND r.archived_at IS NULL
|
|
810
|
+
GROUP BY r.id
|
|
811
|
+
HAVING COUNT(a.id) = 0
|
|
812
|
+
`,
|
|
813
|
+
args: [userId], // bind user_id to the ? placeholder
|
|
814
|
+
});
|
|
815
|
+
// Count how many rollups have zero archived originals
|
|
816
|
+
const staleRollups = staleResult.rows.length;
|
|
817
|
+
// ββ Totals: aggregate counts for health report summary βββββββ
|
|
818
|
+
// Three scalar subqueries in one shot for efficiency.
|
|
819
|
+
const totalsResult = await this.db.execute({
|
|
820
|
+
sql: `
|
|
821
|
+
SELECT
|
|
822
|
+
(SELECT COUNT(*) FROM session_ledger
|
|
823
|
+
WHERE user_id = ? AND archived_at IS NULL) as active,
|
|
824
|
+
(SELECT COUNT(*) FROM session_handoffs
|
|
825
|
+
WHERE user_id = ?) as handoffs,
|
|
826
|
+
(SELECT COUNT(*) FROM session_ledger
|
|
827
|
+
WHERE user_id = ? AND is_rollup = 1
|
|
828
|
+
AND archived_at IS NULL) as rollups
|
|
829
|
+
`,
|
|
830
|
+
args: [userId, userId, userId], // bind user_id 3x (one per subquery)
|
|
831
|
+
});
|
|
832
|
+
// Extract each total, fallback to 0 if undefined
|
|
833
|
+
const totalActiveEntries = Number(totalsResult.rows[0]?.active ?? 0);
|
|
834
|
+
const totalHandoffs = Number(totalsResult.rows[0]?.handoffs ?? 0);
|
|
835
|
+
const totalRollups = Number(totalsResult.rows[0]?.rollups ?? 0);
|
|
836
|
+
// ββ Return the complete raw health stats βββββββββββββββββββββ
|
|
837
|
+
// healthCheck.ts engine will analyze this + produce HealthReport
|
|
838
|
+
return {
|
|
839
|
+
missingEmbeddings, // entries needing embedding repair
|
|
840
|
+
activeLedgerSummaries, // raw summaries for JS dupe detection
|
|
841
|
+
orphanedHandoffs, // projects with handoff but no ledger
|
|
842
|
+
staleRollups, // rollups with no archived originals
|
|
843
|
+
totalActiveEntries, // grand total of active entries
|
|
844
|
+
totalHandoffs, // grand total of handoff records
|
|
845
|
+
totalRollups, // grand total of rollup entries
|
|
846
|
+
};
|
|
847
|
+
}
|
|
729
848
|
}
|