persyst-mcp 2.2.3 → 2.2.4
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/package.json +1 -1
- package/src/database.js +31 -15
- package/src/search.js +11 -5
- package/src/tools.js +3 -3
package/package.json
CHANGED
package/src/database.js
CHANGED
|
@@ -323,13 +323,13 @@ const stmts = {
|
|
|
323
323
|
boost: db.prepare(`
|
|
324
324
|
UPDATE memories
|
|
325
325
|
SET access_count = access_count + 1,
|
|
326
|
-
importance_score = MIN(importance_score + 0.1,
|
|
326
|
+
importance_score = ROUND(MIN(importance_score + 0.1, 1.0), 4),
|
|
327
327
|
last_accessed = unixepoch()
|
|
328
328
|
WHERE id = ?
|
|
329
329
|
`),
|
|
330
330
|
decay: db.prepare(`
|
|
331
331
|
UPDATE memories
|
|
332
|
-
SET importance_score = importance_score * 0.95
|
|
332
|
+
SET importance_score = ROUND(MAX(importance_score * 0.95, 0.0), 4)
|
|
333
333
|
WHERE (? - last_accessed) > 604800
|
|
334
334
|
`),
|
|
335
335
|
|
|
@@ -443,7 +443,8 @@ export function insertMemory(content, importance = 1.0, provenanceInfo = null, n
|
|
|
443
443
|
if (content && content.length > 10000) {
|
|
444
444
|
throw new Error('Memory content exceeds maximum length of 10000 characters.');
|
|
445
445
|
}
|
|
446
|
-
const
|
|
446
|
+
const clampedImportance = Math.max(0.0, Math.min(1.0, Math.round(importance * 10000) / 10000));
|
|
447
|
+
const result = stmts.insertMemory.run(content, clampedImportance, namespace || 'shared', parentId);
|
|
447
448
|
const id = Number(result.lastInsertRowid);
|
|
448
449
|
|
|
449
450
|
// Provenance Info handling
|
|
@@ -510,9 +511,10 @@ export function getAnyMemoryById(id) {
|
|
|
510
511
|
* @returns {object|null} The memory row, or null if not found
|
|
511
512
|
*/
|
|
512
513
|
export function getMemoryById(id, namespace = null) {
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
|
|
514
|
+
const ns = namespace || 'shared';
|
|
515
|
+
const memory = ns === 'all'
|
|
516
|
+
? stmts.getById.get(id)
|
|
517
|
+
: stmts.getByIdNs.get(id, ns);
|
|
516
518
|
if (memory) {
|
|
517
519
|
memory.provenance = getProvenance(id);
|
|
518
520
|
}
|
|
@@ -557,12 +559,17 @@ export function deleteMemory(id) {
|
|
|
557
559
|
/**
|
|
558
560
|
* Get the N most recently created memories.
|
|
559
561
|
* @param {number} limit - Max results
|
|
560
|
-
* @param {string|null} namespace - Namespace filter (null =
|
|
562
|
+
* @param {string|null} namespace - Namespace filter (null = shared)
|
|
561
563
|
*/
|
|
562
564
|
export function getRecentMemories(limit = 10, namespace = null) {
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
565
|
+
if (typeof limit !== 'number' || isNaN(limit) || limit <= 0) {
|
|
566
|
+
throw new Error('Limit must be a positive integer.');
|
|
567
|
+
}
|
|
568
|
+
const parsedLimit = Math.floor(limit);
|
|
569
|
+
const ns = namespace || 'shared';
|
|
570
|
+
const rows = ns === 'all'
|
|
571
|
+
? stmts.getRecent.all(parsedLimit)
|
|
572
|
+
: stmts.getRecentNs.all(ns, parsedLimit);
|
|
566
573
|
rows.forEach(r => {
|
|
567
574
|
r.provenance = getProvenance(r.id);
|
|
568
575
|
});
|
|
@@ -572,12 +579,17 @@ export function getRecentMemories(limit = 10, namespace = null) {
|
|
|
572
579
|
/**
|
|
573
580
|
* Get the N most important memories (by importance_score).
|
|
574
581
|
* @param {number} limit - Max results
|
|
575
|
-
* @param {string|null} namespace - Namespace filter (null =
|
|
582
|
+
* @param {string|null} namespace - Namespace filter (null = shared)
|
|
576
583
|
*/
|
|
577
584
|
export function getImportantMemories(limit = 10, namespace = null) {
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
585
|
+
if (typeof limit !== 'number' || isNaN(limit) || limit <= 0) {
|
|
586
|
+
throw new Error('Limit must be a positive integer.');
|
|
587
|
+
}
|
|
588
|
+
const parsedLimit = Math.floor(limit);
|
|
589
|
+
const ns = namespace || 'shared';
|
|
590
|
+
const rows = ns === 'all'
|
|
591
|
+
? stmts.getImportant.all(parsedLimit)
|
|
592
|
+
: stmts.getImportantNs.all(ns, parsedLimit);
|
|
581
593
|
rows.forEach(r => {
|
|
582
594
|
r.provenance = getProvenance(r.id);
|
|
583
595
|
});
|
|
@@ -633,7 +645,11 @@ export function searchKeyword(query, limit = 10) {
|
|
|
633
645
|
* @returns {Array<{rowid: number, distance: number}>}
|
|
634
646
|
*/
|
|
635
647
|
export function searchVector(embedding, limit = 10) {
|
|
636
|
-
|
|
648
|
+
if (typeof limit !== 'number' || isNaN(limit) || limit <= 0) {
|
|
649
|
+
throw new Error('Limit must be a positive integer.');
|
|
650
|
+
}
|
|
651
|
+
const parsedLimit = Math.floor(limit);
|
|
652
|
+
return stmts.searchVec.all(Buffer.from(embedding.buffer), parsedLimit);
|
|
637
653
|
}
|
|
638
654
|
|
|
639
655
|
// ============================================================
|
package/src/search.js
CHANGED
|
@@ -33,6 +33,12 @@ let lastDataVersion = 0;
|
|
|
33
33
|
* @returns {Promise<Array>} Ranked search results (with .attestation property attached)
|
|
34
34
|
*/
|
|
35
35
|
export async function searchHybrid(queryText, limit = 5, agentId = null, sessionId = null, namespace = null, skipAttestation = false) {
|
|
36
|
+
if (typeof limit !== 'number' || isNaN(limit) || limit <= 0) {
|
|
37
|
+
throw new Error('Limit must be a positive integer.');
|
|
38
|
+
}
|
|
39
|
+
const parsedLimit = Math.floor(limit);
|
|
40
|
+
const ns = namespace || 'shared';
|
|
41
|
+
|
|
36
42
|
// Sync in-memory cache with external DB changes using sqlite data_version
|
|
37
43
|
try {
|
|
38
44
|
const currentDataVersion = db.pragma('data_version', { simple: true });
|
|
@@ -46,7 +52,7 @@ export async function searchHybrid(queryText, limit = 5, agentId = null, session
|
|
|
46
52
|
|
|
47
53
|
// --- Check LRU cache first (Feature 1) ---
|
|
48
54
|
// Include namespace in cache key to prevent cross-namespace cache hits
|
|
49
|
-
const cacheKey = LRUCache.key(`${
|
|
55
|
+
const cacheKey = LRUCache.key(`${ns}:${queryText}`, parsedLimit);
|
|
50
56
|
const cached = searchCache.get(cacheKey);
|
|
51
57
|
if (cached) {
|
|
52
58
|
console.error(`[persyst-cache] Cache HIT for query: "${queryText.slice(0, 50)}..."`);
|
|
@@ -54,12 +60,12 @@ export async function searchHybrid(queryText, limit = 5, agentId = null, session
|
|
|
54
60
|
}
|
|
55
61
|
|
|
56
62
|
// --- Step 1: Keyword search (fast, exact matches) ---
|
|
57
|
-
const keywordHits = searchKeyword(queryText,
|
|
63
|
+
const keywordHits = searchKeyword(queryText, parsedLimit * 2);
|
|
58
64
|
const keywordIds = new Set(keywordHits.map(r => r.id));
|
|
59
65
|
|
|
60
66
|
// --- Step 2: Semantic search (meaning-based) ---
|
|
61
67
|
const queryEmbedding = await generateEmbedding(queryText);
|
|
62
|
-
const vecHits = searchVector(queryEmbedding,
|
|
68
|
+
const vecHits = searchVector(queryEmbedding, parsedLimit * 2);
|
|
63
69
|
|
|
64
70
|
const semanticResults = vecHits.map(r => ({
|
|
65
71
|
id: r.rowid,
|
|
@@ -99,7 +105,7 @@ export async function searchHybrid(queryText, limit = 5, agentId = null, session
|
|
|
99
105
|
const finalResults = combined
|
|
100
106
|
.map(r => {
|
|
101
107
|
// Use namespace-aware getMemoryById to filter by agent namespace
|
|
102
|
-
const memory = getMemoryById(r.id,
|
|
108
|
+
const memory = getMemoryById(r.id, ns);
|
|
103
109
|
if (!memory) return null; // Memory was archived, deleted, or not in namespace
|
|
104
110
|
|
|
105
111
|
// Boost memory access metrics
|
|
@@ -141,7 +147,7 @@ export async function searchHybrid(queryText, limit = 5, agentId = null, session
|
|
|
141
147
|
finalResults.sort((a, b) => parseFloat(b.hybrid_score) - parseFloat(a.hybrid_score));
|
|
142
148
|
|
|
143
149
|
// --- Step 5: Apply MMR for diverse retrieval (Feature 3) ---
|
|
144
|
-
const mmrResults = applyMMR(finalResults,
|
|
150
|
+
const mmrResults = applyMMR(finalResults, parsedLimit);
|
|
145
151
|
|
|
146
152
|
// Generate cryptographic attestation for audit trails (skip if called internally)
|
|
147
153
|
let attestation = null;
|
package/src/tools.js
CHANGED
|
@@ -290,7 +290,7 @@ export function registerTools(server) {
|
|
|
290
290
|
'Search memories using hybrid keyword + semantic search with cryptographic attestation. CRITICAL: Call this tool at the start of a session or task to retrieve relevant user preferences, coding guidelines, and past decisions.',
|
|
291
291
|
{
|
|
292
292
|
query: z.string().describe('What to search for'),
|
|
293
|
-
limit: z.number().default(5).describe('Max results (default: 5)'),
|
|
293
|
+
limit: z.number().int().min(1).default(5).describe('Max results (default: 5)'),
|
|
294
294
|
agent_id: z.string().optional().describe('Agent ID — filters results to this agent\'s namespace + shared'),
|
|
295
295
|
session_id: z.string().optional().describe('Session ID')
|
|
296
296
|
},
|
|
@@ -415,7 +415,7 @@ export function registerTools(server) {
|
|
|
415
415
|
'get_recent_memories',
|
|
416
416
|
'Get the most recently created memories, newest first. Filtered by agent namespace if agent_id is provided.',
|
|
417
417
|
{
|
|
418
|
-
limit: z.number().default(10).describe('How many to return (default: 10)'),
|
|
418
|
+
limit: z.number().int().min(1).default(10).describe('How many to return (default: 10)'),
|
|
419
419
|
agent_id: z.string().optional().describe('Agent ID — filters to this agent\'s namespace + shared')
|
|
420
420
|
},
|
|
421
421
|
async ({ limit, agent_id }) => {
|
|
@@ -434,7 +434,7 @@ export function registerTools(server) {
|
|
|
434
434
|
'get_important_memories',
|
|
435
435
|
'Get memories ranked by importance score, highest first. Filtered by agent namespace if agent_id is provided.',
|
|
436
436
|
{
|
|
437
|
-
limit: z.number().default(10).describe('How many to return (default: 10)'),
|
|
437
|
+
limit: z.number().int().min(1).default(10).describe('How many to return (default: 10)'),
|
|
438
438
|
agent_id: z.string().optional().describe('Agent ID — filters to this agent\'s namespace + shared')
|
|
439
439
|
},
|
|
440
440
|
async ({ limit, agent_id }) => {
|