server-memory-enhanced 2.3.2 → 3.0.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/dist/index.js +45 -64
- package/dist/lib/analysis/conflict-detector.js +4 -2
- package/dist/lib/analysis/context-builder.js +10 -7
- package/dist/lib/analysis/memory-stats.js +26 -22
- package/dist/lib/analysis/path-finder.js +4 -3
- package/dist/lib/collaboration/flag-manager.js +5 -3
- package/dist/lib/knowledge-graph-manager.js +22 -22
- package/dist/lib/queries/entity-queries.js +26 -24
- package/dist/lib/queries/graph-reader.js +14 -3
- package/dist/lib/queries/search-service.js +32 -18
- package/dist/lib/schemas.js +61 -6
- package/dist/lib/versioning/observation-history.js +11 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { promises as fs } from 'fs';
|
|
|
6
6
|
import path from 'path';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
8
|
import { KnowledgeGraphManager } from './lib/knowledge-graph-manager.js';
|
|
9
|
-
import { EntitySchema, RelationSchema, SaveMemoryInputSchema, SaveMemoryOutputSchema, GetAnalyticsInputSchema, GetAnalyticsOutputSchema, GetObservationHistoryInputSchema, GetObservationHistoryOutputSchema, ListEntitiesInputSchema, ListEntitiesOutputSchema, ValidateMemoryInputSchema, ValidateMemoryOutputSchema, UpdateObservationInputSchema, UpdateObservationOutputSchema } from './lib/schemas.js';
|
|
9
|
+
import { EntitySchema, RelationSchema, SaveMemoryInputSchema, SaveMemoryOutputSchema, GetAnalyticsInputSchema, GetAnalyticsOutputSchema, GetObservationHistoryInputSchema, GetObservationHistoryOutputSchema, ListEntitiesInputSchema, ListEntitiesOutputSchema, ValidateMemoryInputSchema, ValidateMemoryOutputSchema, UpdateObservationInputSchema, UpdateObservationOutputSchema, ReadGraphInputSchema, SearchNodesInputSchema, OpenNodesInputSchema, QueryNodesInputSchema, GetMemoryStatsInputSchema, GetRecentChangesInputSchema, FindRelationPathInputSchema, DetectConflictsInputSchema, GetFlaggedEntitiesInputSchema, GetContextInputSchema } from './lib/schemas.js';
|
|
10
10
|
import { handleSaveMemory } from './lib/save-memory-handler.js';
|
|
11
11
|
import { validateSaveMemoryRequest } from './lib/validation.js';
|
|
12
12
|
import { JsonlStorageAdapter } from './lib/jsonl-storage-adapter.js';
|
|
@@ -314,14 +314,14 @@ server.registerTool("delete_relations", {
|
|
|
314
314
|
// Register read_graph tool
|
|
315
315
|
server.registerTool("read_graph", {
|
|
316
316
|
title: "Read Graph",
|
|
317
|
-
description: "Read the
|
|
318
|
-
inputSchema:
|
|
317
|
+
description: "Read the knowledge graph for a specific thread (thread isolation enforced)",
|
|
318
|
+
inputSchema: ReadGraphInputSchema,
|
|
319
319
|
outputSchema: {
|
|
320
320
|
entities: z.array(EntitySchemaCompat),
|
|
321
321
|
relations: z.array(RelationSchemaCompat)
|
|
322
322
|
}
|
|
323
|
-
}, async () => {
|
|
324
|
-
const graph = await knowledgeGraphManager.readGraph();
|
|
323
|
+
}, async (input) => {
|
|
324
|
+
const graph = await knowledgeGraphManager.readGraph(input.threadId);
|
|
325
325
|
return {
|
|
326
326
|
content: [{ type: "text", text: JSON.stringify(graph, null, 2) }],
|
|
327
327
|
structuredContent: { ...graph }
|
|
@@ -330,16 +330,14 @@ server.registerTool("read_graph", {
|
|
|
330
330
|
// Register search_nodes tool
|
|
331
331
|
server.registerTool("search_nodes", {
|
|
332
332
|
title: "Search Nodes",
|
|
333
|
-
description: "Search for nodes in the knowledge graph based on a query",
|
|
334
|
-
inputSchema:
|
|
335
|
-
query: z.string().describe("The search query to match against entity names, types, and observation content")
|
|
336
|
-
},
|
|
333
|
+
description: "Search for nodes in the knowledge graph based on a query (thread isolation enforced)",
|
|
334
|
+
inputSchema: SearchNodesInputSchema,
|
|
337
335
|
outputSchema: {
|
|
338
336
|
entities: z.array(EntitySchemaCompat),
|
|
339
337
|
relations: z.array(RelationSchemaCompat)
|
|
340
338
|
}
|
|
341
|
-
}, async (
|
|
342
|
-
const graph = await knowledgeGraphManager.searchNodes(query);
|
|
339
|
+
}, async (input) => {
|
|
340
|
+
const graph = await knowledgeGraphManager.searchNodes(input.threadId, input.query);
|
|
343
341
|
return {
|
|
344
342
|
content: [{ type: "text", text: JSON.stringify(graph, null, 2) }],
|
|
345
343
|
structuredContent: { ...graph }
|
|
@@ -348,16 +346,14 @@ server.registerTool("search_nodes", {
|
|
|
348
346
|
// Register open_nodes tool
|
|
349
347
|
server.registerTool("open_nodes", {
|
|
350
348
|
title: "Open Nodes",
|
|
351
|
-
description: "Open specific nodes in the knowledge graph by their names",
|
|
352
|
-
inputSchema:
|
|
353
|
-
names: z.array(z.string()).describe("An array of entity names to retrieve")
|
|
354
|
-
},
|
|
349
|
+
description: "Open specific nodes in the knowledge graph by their names (thread isolation enforced)",
|
|
350
|
+
inputSchema: OpenNodesInputSchema,
|
|
355
351
|
outputSchema: {
|
|
356
352
|
entities: z.array(EntitySchemaCompat),
|
|
357
353
|
relations: z.array(RelationSchemaCompat)
|
|
358
354
|
}
|
|
359
|
-
}, async (
|
|
360
|
-
const graph = await knowledgeGraphManager.openNodes(names);
|
|
355
|
+
}, async (input) => {
|
|
356
|
+
const graph = await knowledgeGraphManager.openNodes(input.threadId, input.names);
|
|
361
357
|
return {
|
|
362
358
|
content: [{ type: "text", text: JSON.stringify(graph, null, 2) }],
|
|
363
359
|
structuredContent: { ...graph }
|
|
@@ -366,21 +362,15 @@ server.registerTool("open_nodes", {
|
|
|
366
362
|
// Register query_nodes tool for advanced filtering
|
|
367
363
|
server.registerTool("query_nodes", {
|
|
368
364
|
title: "Query Nodes",
|
|
369
|
-
description: "Query nodes and relations in the knowledge graph with advanced filtering by timestamp, confidence, and importance ranges",
|
|
370
|
-
inputSchema:
|
|
371
|
-
timestampStart: z.string().optional().describe("ISO 8601 timestamp - filter for items created on or after this time"),
|
|
372
|
-
timestampEnd: z.string().optional().describe("ISO 8601 timestamp - filter for items created on or before this time"),
|
|
373
|
-
confidenceMin: z.number().min(0).max(1).optional().describe("Minimum confidence value (0-1)"),
|
|
374
|
-
confidenceMax: z.number().min(0).max(1).optional().describe("Maximum confidence value (0-1)"),
|
|
375
|
-
importanceMin: z.number().min(0).max(1).optional().describe("Minimum importance value (0-1)"),
|
|
376
|
-
importanceMax: z.number().min(0).max(1).optional().describe("Maximum importance value (0-1)")
|
|
377
|
-
},
|
|
365
|
+
description: "Query nodes and relations in the knowledge graph with advanced filtering by timestamp, confidence, and importance ranges (thread isolation enforced)",
|
|
366
|
+
inputSchema: QueryNodesInputSchema,
|
|
378
367
|
outputSchema: {
|
|
379
368
|
entities: z.array(EntitySchemaCompat),
|
|
380
369
|
relations: z.array(RelationSchemaCompat)
|
|
381
370
|
}
|
|
382
|
-
}, async (
|
|
383
|
-
const
|
|
371
|
+
}, async (input) => {
|
|
372
|
+
const { threadId, ...filters } = input;
|
|
373
|
+
const graph = await knowledgeGraphManager.queryNodes(threadId, Object.keys(filters).length > 0 ? filters : undefined);
|
|
384
374
|
return {
|
|
385
375
|
content: [{ type: "text", text: JSON.stringify(graph, null, 2) }],
|
|
386
376
|
structuredContent: { ...graph }
|
|
@@ -389,7 +379,7 @@ server.registerTool("query_nodes", {
|
|
|
389
379
|
// Register list_entities tool for simple entity lookup
|
|
390
380
|
server.registerTool("list_entities", {
|
|
391
381
|
title: "List Entities",
|
|
392
|
-
description: "List entities with optional filtering by entity type and name pattern. Returns a simple list of entity names and types for quick discovery.",
|
|
382
|
+
description: "List entities with optional filtering by entity type and name pattern. Returns a simple list of entity names and types for quick discovery. Thread isolation enforced - only shows entities from the specified thread.",
|
|
393
383
|
inputSchema: ListEntitiesInputSchema,
|
|
394
384
|
outputSchema: ListEntitiesOutputSchema
|
|
395
385
|
}, async (input) => {
|
|
@@ -522,8 +512,8 @@ server.registerTool("validate_memory", {
|
|
|
522
512
|
// Register get_memory_stats tool
|
|
523
513
|
server.registerTool("get_memory_stats", {
|
|
524
514
|
title: "Get Memory Statistics",
|
|
525
|
-
description: "Get comprehensive statistics about the knowledge graph including entity counts,
|
|
526
|
-
inputSchema:
|
|
515
|
+
description: "Get comprehensive statistics about the knowledge graph for a specific thread including entity counts, activity, and confidence/importance metrics (thread isolation enforced)",
|
|
516
|
+
inputSchema: GetMemoryStatsInputSchema,
|
|
527
517
|
outputSchema: {
|
|
528
518
|
entityCount: z.number(),
|
|
529
519
|
relationCount: z.number(),
|
|
@@ -536,8 +526,8 @@ server.registerTool("get_memory_stats", {
|
|
|
536
526
|
entityCount: z.number()
|
|
537
527
|
}))
|
|
538
528
|
}
|
|
539
|
-
}, async () => {
|
|
540
|
-
const stats = await knowledgeGraphManager.getMemoryStats();
|
|
529
|
+
}, async (input) => {
|
|
530
|
+
const stats = await knowledgeGraphManager.getMemoryStats(input.threadId);
|
|
541
531
|
return {
|
|
542
532
|
content: [{ type: "text", text: JSON.stringify(stats, null, 2) }],
|
|
543
533
|
structuredContent: stats
|
|
@@ -546,16 +536,14 @@ server.registerTool("get_memory_stats", {
|
|
|
546
536
|
// Register get_recent_changes tool
|
|
547
537
|
server.registerTool("get_recent_changes", {
|
|
548
538
|
title: "Get Recent Changes",
|
|
549
|
-
description: "Retrieve entities and relations that were created or modified since a specific timestamp",
|
|
550
|
-
inputSchema:
|
|
551
|
-
since: z.string().describe("ISO 8601 timestamp - return changes since this time")
|
|
552
|
-
},
|
|
539
|
+
description: "Retrieve entities and relations that were created or modified since a specific timestamp (thread isolation enforced)",
|
|
540
|
+
inputSchema: GetRecentChangesInputSchema,
|
|
553
541
|
outputSchema: {
|
|
554
542
|
entities: z.array(EntitySchemaCompat),
|
|
555
543
|
relations: z.array(RelationSchemaCompat)
|
|
556
544
|
}
|
|
557
|
-
}, async (
|
|
558
|
-
const changes = await knowledgeGraphManager.getRecentChanges(since);
|
|
545
|
+
}, async (input) => {
|
|
546
|
+
const changes = await knowledgeGraphManager.getRecentChanges(input.threadId, input.since);
|
|
559
547
|
return {
|
|
560
548
|
content: [{ type: "text", text: JSON.stringify(changes, null, 2) }],
|
|
561
549
|
structuredContent: { ...changes }
|
|
@@ -564,19 +552,15 @@ server.registerTool("get_recent_changes", {
|
|
|
564
552
|
// Register find_relation_path tool
|
|
565
553
|
server.registerTool("find_relation_path", {
|
|
566
554
|
title: "Find Relationship Path",
|
|
567
|
-
description: "Find a path of relationships connecting two entities in the knowledge graph",
|
|
568
|
-
inputSchema:
|
|
569
|
-
from: z.string().describe("Starting entity name"),
|
|
570
|
-
to: z.string().describe("Target entity name"),
|
|
571
|
-
maxDepth: z.number().optional().default(5).describe("Maximum path depth to search (default: 5)")
|
|
572
|
-
},
|
|
555
|
+
description: "Find a path of relationships connecting two entities in the knowledge graph (thread isolation enforced)",
|
|
556
|
+
inputSchema: FindRelationPathInputSchema,
|
|
573
557
|
outputSchema: {
|
|
574
558
|
found: z.boolean(),
|
|
575
559
|
path: z.array(z.string()),
|
|
576
560
|
relations: z.array(RelationSchemaCompat)
|
|
577
561
|
}
|
|
578
|
-
}, async (
|
|
579
|
-
const result = await knowledgeGraphManager.findRelationPath(from, to, maxDepth || 5);
|
|
562
|
+
}, async (input) => {
|
|
563
|
+
const result = await knowledgeGraphManager.findRelationPath(input.threadId, input.from, input.to, input.maxDepth || 5);
|
|
580
564
|
return {
|
|
581
565
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
582
566
|
structuredContent: result
|
|
@@ -585,8 +569,8 @@ server.registerTool("find_relation_path", {
|
|
|
585
569
|
// Register detect_conflicts tool
|
|
586
570
|
server.registerTool("detect_conflicts", {
|
|
587
571
|
title: "Detect Conflicts",
|
|
588
|
-
description: "Detect potentially conflicting observations within entities using pattern matching and negation detection",
|
|
589
|
-
inputSchema:
|
|
572
|
+
description: "Detect potentially conflicting observations within entities using pattern matching and negation detection (thread isolation enforced)",
|
|
573
|
+
inputSchema: DetectConflictsInputSchema,
|
|
590
574
|
outputSchema: {
|
|
591
575
|
conflicts: z.array(z.object({
|
|
592
576
|
entityName: z.string(),
|
|
@@ -597,8 +581,8 @@ server.registerTool("detect_conflicts", {
|
|
|
597
581
|
}))
|
|
598
582
|
}))
|
|
599
583
|
}
|
|
600
|
-
}, async () => {
|
|
601
|
-
const conflicts = await knowledgeGraphManager.detectConflicts();
|
|
584
|
+
}, async (input) => {
|
|
585
|
+
const conflicts = await knowledgeGraphManager.detectConflicts(input.threadId);
|
|
602
586
|
return {
|
|
603
587
|
content: [{ type: "text", text: JSON.stringify({ conflicts }, null, 2) }],
|
|
604
588
|
structuredContent: { conflicts }
|
|
@@ -670,13 +654,13 @@ server.registerTool("flag_for_review", {
|
|
|
670
654
|
// Register get_flagged_entities tool
|
|
671
655
|
server.registerTool("get_flagged_entities", {
|
|
672
656
|
title: "Get Flagged Entities",
|
|
673
|
-
description: "Retrieve all entities that have been flagged for human review",
|
|
674
|
-
inputSchema:
|
|
657
|
+
description: "Retrieve all entities that have been flagged for human review (thread isolation enforced)",
|
|
658
|
+
inputSchema: GetFlaggedEntitiesInputSchema,
|
|
675
659
|
outputSchema: {
|
|
676
660
|
entities: z.array(EntitySchemaCompat)
|
|
677
661
|
}
|
|
678
|
-
}, async () => {
|
|
679
|
-
const entities = await knowledgeGraphManager.getFlaggedEntities();
|
|
662
|
+
}, async (input) => {
|
|
663
|
+
const entities = await knowledgeGraphManager.getFlaggedEntities(input.threadId);
|
|
680
664
|
return {
|
|
681
665
|
content: [{ type: "text", text: JSON.stringify({ entities }, null, 2) }],
|
|
682
666
|
structuredContent: { entities }
|
|
@@ -685,17 +669,14 @@ server.registerTool("get_flagged_entities", {
|
|
|
685
669
|
// Register get_context tool
|
|
686
670
|
server.registerTool("get_context", {
|
|
687
671
|
title: "Get Context",
|
|
688
|
-
description: "Retrieve entities and relations related to specified entities up to a certain depth, useful for understanding context around specific topics",
|
|
689
|
-
inputSchema:
|
|
690
|
-
entityNames: z.array(z.string()).describe("Names of entities to get context for"),
|
|
691
|
-
depth: z.number().optional().default(1).describe("How many relationship hops to include (default: 1)")
|
|
692
|
-
},
|
|
672
|
+
description: "Retrieve entities and relations related to specified entities up to a certain depth, useful for understanding context around specific topics (thread isolation enforced)",
|
|
673
|
+
inputSchema: GetContextInputSchema,
|
|
693
674
|
outputSchema: {
|
|
694
675
|
entities: z.array(EntitySchemaCompat),
|
|
695
676
|
relations: z.array(RelationSchemaCompat)
|
|
696
677
|
}
|
|
697
|
-
}, async (
|
|
698
|
-
const context = await knowledgeGraphManager.getContext(entityNames, depth || 1);
|
|
678
|
+
}, async (input) => {
|
|
679
|
+
const context = await knowledgeGraphManager.getContext(input.threadId, input.entityNames, input.depth || 1);
|
|
699
680
|
return {
|
|
700
681
|
content: [{ type: "text", text: JSON.stringify(context, null, 2) }],
|
|
701
682
|
structuredContent: { ...context }
|
|
@@ -738,11 +719,11 @@ server.registerTool("get_analytics", {
|
|
|
738
719
|
// Register get_observation_history tool
|
|
739
720
|
server.registerTool("get_observation_history", {
|
|
740
721
|
title: "Get Observation History",
|
|
741
|
-
description: "Retrieve the full version chain for a specific observation, showing how it evolved over time",
|
|
722
|
+
description: "Retrieve the full version chain for a specific observation, showing how it evolved over time (thread isolation enforced)",
|
|
742
723
|
inputSchema: GetObservationHistoryInputSchema,
|
|
743
724
|
outputSchema: GetObservationHistoryOutputSchema
|
|
744
725
|
}, async (input) => {
|
|
745
|
-
const result = await knowledgeGraphManager.getObservationHistory(input.entityName, input.observationId);
|
|
726
|
+
const result = await knowledgeGraphManager.getObservationHistory(input.threadId, input.entityName, input.observationId);
|
|
746
727
|
return {
|
|
747
728
|
content: [{ type: "text", text: JSON.stringify({ history: result }, null, 2) }],
|
|
748
729
|
structuredContent: { history: result }
|
|
@@ -5,11 +5,13 @@ import { hasNegation, NEGATION_WORDS } from '../utils/negation-detector.js';
|
|
|
5
5
|
/**
|
|
6
6
|
* Detect conflicting observations within entities
|
|
7
7
|
* Identifies potential contradictions by checking for negation patterns
|
|
8
|
+
* Filtered by threadId for thread isolation
|
|
8
9
|
*/
|
|
9
|
-
export async function detectConflicts(storage) {
|
|
10
|
+
export async function detectConflicts(storage, threadId) {
|
|
10
11
|
const graph = await storage.loadGraph();
|
|
11
12
|
const conflicts = [];
|
|
12
|
-
|
|
13
|
+
// Only analyze entities in the specified thread
|
|
14
|
+
for (const entity of graph.entities.filter(e => e.agentThreadId === threadId)) {
|
|
13
15
|
const entityConflicts = [];
|
|
14
16
|
for (let i = 0; i < entity.observations.length; i++) {
|
|
15
17
|
for (let j = i + 1; j < entity.observations.length; j++) {
|
|
@@ -4,16 +4,18 @@
|
|
|
4
4
|
/**
|
|
5
5
|
* Get context (entities related to specified entities up to a certain depth)
|
|
6
6
|
* Expands to include related entities up to specified depth
|
|
7
|
+
* Filtered by threadId for thread isolation
|
|
7
8
|
*/
|
|
8
|
-
export async function getContext(storage, entityNames, depth = 1) {
|
|
9
|
+
export async function getContext(storage, threadId, entityNames, depth = 1) {
|
|
9
10
|
const graph = await storage.loadGraph();
|
|
10
11
|
const contextEntityNames = new Set(entityNames);
|
|
11
|
-
// Expand to include related entities up to specified depth
|
|
12
|
+
// Expand to include related entities up to specified depth - only within this thread
|
|
12
13
|
for (let d = 0; d < depth; d++) {
|
|
13
14
|
const currentEntities = Array.from(contextEntityNames);
|
|
14
15
|
for (const entityName of currentEntities) {
|
|
15
|
-
// Find all relations involving this entity
|
|
16
|
-
const relatedRelations = graph.relations.filter(r => r.
|
|
16
|
+
// Find all relations involving this entity - only from this thread
|
|
17
|
+
const relatedRelations = graph.relations.filter(r => r.agentThreadId === threadId &&
|
|
18
|
+
(r.from === entityName || r.to === entityName));
|
|
17
19
|
// Add related entities
|
|
18
20
|
relatedRelations.forEach(r => {
|
|
19
21
|
contextEntityNames.add(r.from);
|
|
@@ -21,9 +23,10 @@ export async function getContext(storage, entityNames, depth = 1) {
|
|
|
21
23
|
});
|
|
22
24
|
}
|
|
23
25
|
}
|
|
24
|
-
// Get all entities and relations in context
|
|
25
|
-
const contextEntities = graph.entities.filter(e => contextEntityNames.has(e.name));
|
|
26
|
-
const contextRelations = graph.relations.filter(r =>
|
|
26
|
+
// Get all entities and relations in context - only from this thread
|
|
27
|
+
const contextEntities = graph.entities.filter(e => e.agentThreadId === threadId && contextEntityNames.has(e.name));
|
|
28
|
+
const contextRelations = graph.relations.filter(r => r.agentThreadId === threadId &&
|
|
29
|
+
contextEntityNames.has(r.from) && contextEntityNames.has(r.to));
|
|
27
30
|
return {
|
|
28
31
|
entities: contextEntities,
|
|
29
32
|
relations: contextRelations
|
|
@@ -2,31 +2,34 @@
|
|
|
2
2
|
* Memory statistics service
|
|
3
3
|
*/
|
|
4
4
|
/**
|
|
5
|
-
* Get comprehensive memory statistics
|
|
5
|
+
* Get comprehensive memory statistics for a specific thread
|
|
6
|
+
* Filtered by threadId for thread isolation
|
|
6
7
|
*/
|
|
7
|
-
export async function getMemoryStats(storage) {
|
|
8
|
+
export async function getMemoryStats(storage, threadId) {
|
|
8
9
|
const graph = await storage.loadGraph();
|
|
10
|
+
// Filter entities and relations by threadId
|
|
11
|
+
const threadEntities = graph.entities.filter(e => e.agentThreadId === threadId);
|
|
12
|
+
const threadRelations = graph.relations.filter(r => r.agentThreadId === threadId);
|
|
9
13
|
// Count entity types
|
|
10
14
|
const entityTypes = {};
|
|
11
|
-
|
|
15
|
+
threadEntities.forEach(e => {
|
|
12
16
|
entityTypes[e.entityType] = (entityTypes[e.entityType] || 0) + 1;
|
|
13
17
|
});
|
|
14
18
|
// Calculate averages
|
|
15
|
-
const avgConfidence =
|
|
16
|
-
?
|
|
19
|
+
const avgConfidence = threadEntities.length > 0
|
|
20
|
+
? threadEntities.reduce((sum, e) => sum + e.confidence, 0) / threadEntities.length
|
|
17
21
|
: 0;
|
|
18
|
-
const avgImportance =
|
|
19
|
-
?
|
|
22
|
+
const avgImportance = threadEntities.length > 0
|
|
23
|
+
? threadEntities.reduce((sum, e) => sum + e.importance, 0) / threadEntities.length
|
|
20
24
|
: 0;
|
|
21
|
-
// Count unique threads
|
|
22
|
-
const threads = new Set(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
// Recent activity (last 7 days, grouped by day)
|
|
25
|
+
// Count unique threads in the system (across all entities in the graph)
|
|
26
|
+
const threads = new Set(graph.entities
|
|
27
|
+
.map(e => e.agentThreadId)
|
|
28
|
+
.filter((id) => !!id));
|
|
29
|
+
// Recent activity (last 7 days, grouped by day) - only for this thread
|
|
27
30
|
const now = new Date();
|
|
28
31
|
const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
|
29
|
-
const recentEntities =
|
|
32
|
+
const recentEntities = threadEntities.filter(e => new Date(e.timestamp) >= sevenDaysAgo);
|
|
30
33
|
// Group by day
|
|
31
34
|
const activityByDay = {};
|
|
32
35
|
recentEntities.forEach(e => {
|
|
@@ -37,8 +40,8 @@ export async function getMemoryStats(storage) {
|
|
|
37
40
|
.map(([timestamp, entityCount]) => ({ timestamp, entityCount }))
|
|
38
41
|
.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
39
42
|
return {
|
|
40
|
-
entityCount:
|
|
41
|
-
relationCount:
|
|
43
|
+
entityCount: threadEntities.length,
|
|
44
|
+
relationCount: threadRelations.length,
|
|
42
45
|
threadCount: threads.size,
|
|
43
46
|
entityTypes,
|
|
44
47
|
avgConfidence,
|
|
@@ -47,15 +50,16 @@ export async function getMemoryStats(storage) {
|
|
|
47
50
|
};
|
|
48
51
|
}
|
|
49
52
|
/**
|
|
50
|
-
* Get recent changes since a specific timestamp
|
|
53
|
+
* Get recent changes since a specific timestamp for a specific thread
|
|
54
|
+
* Filtered by threadId for thread isolation
|
|
51
55
|
*/
|
|
52
|
-
export async function getRecentChanges(storage, since) {
|
|
56
|
+
export async function getRecentChanges(storage, threadId, since) {
|
|
53
57
|
const graph = await storage.loadGraph();
|
|
54
58
|
const sinceDate = new Date(since);
|
|
55
|
-
// Only return entities and relations that were
|
|
56
|
-
const recentEntities = graph.entities.filter(e => new Date(e.timestamp) >= sinceDate);
|
|
57
|
-
// Only include relations that are recent themselves
|
|
58
|
-
const recentRelations = graph.relations.filter(r => new Date(r.timestamp) >= sinceDate);
|
|
59
|
+
// Only return entities and relations from this thread that were modified since the specified time
|
|
60
|
+
const recentEntities = graph.entities.filter(e => e.agentThreadId === threadId && new Date(e.timestamp) >= sinceDate);
|
|
61
|
+
// Only include relations from this thread that are recent themselves
|
|
62
|
+
const recentRelations = graph.relations.filter(r => r.agentThreadId === threadId && new Date(r.timestamp) >= sinceDate);
|
|
59
63
|
return {
|
|
60
64
|
entities: recentEntities,
|
|
61
65
|
relations: recentRelations
|
|
@@ -4,16 +4,17 @@
|
|
|
4
4
|
/**
|
|
5
5
|
* Find the shortest path between two entities in the knowledge graph
|
|
6
6
|
* Uses BFS algorithm with bidirectional search
|
|
7
|
+
* Filtered by threadId for thread isolation
|
|
7
8
|
*/
|
|
8
|
-
export async function findRelationPath(storage, from, to, maxDepth = 5) {
|
|
9
|
+
export async function findRelationPath(storage, threadId, from, to, maxDepth = 5) {
|
|
9
10
|
const graph = await storage.loadGraph();
|
|
10
11
|
if (from === to) {
|
|
11
12
|
return { found: true, path: [from], relations: [] };
|
|
12
13
|
}
|
|
13
|
-
// Build indexes for efficient relation lookup
|
|
14
|
+
// Build indexes for efficient relation lookup - only for relations in this thread
|
|
14
15
|
const relationsFrom = new Map();
|
|
15
16
|
const relationsTo = new Map();
|
|
16
|
-
for (const rel of graph.relations) {
|
|
17
|
+
for (const rel of graph.relations.filter(r => r.agentThreadId === threadId)) {
|
|
17
18
|
if (!relationsFrom.has(rel.from)) {
|
|
18
19
|
relationsFrom.set(rel.from, []);
|
|
19
20
|
}
|
|
@@ -30,9 +30,11 @@ export async function flagForReview(storage, entityName, reason, reviewer) {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
/**
|
|
33
|
-
* Get all entities flagged for review
|
|
33
|
+
* Get all entities flagged for review in a specific thread
|
|
34
|
+
* Filtered by threadId for thread isolation
|
|
34
35
|
*/
|
|
35
|
-
export async function getFlaggedEntities(storage) {
|
|
36
|
+
export async function getFlaggedEntities(storage, threadId) {
|
|
36
37
|
const graph = await storage.loadGraph();
|
|
37
|
-
return graph.entities.filter(e => e.
|
|
38
|
+
return graph.entities.filter(e => e.agentThreadId === threadId &&
|
|
39
|
+
e.observations.some(obs => obs.content.includes('[FLAGGED FOR REVIEW:')));
|
|
38
40
|
}
|
|
@@ -74,22 +74,22 @@ export class KnowledgeGraphManager {
|
|
|
74
74
|
return ObservationOps.updateObservation(this.storage, params);
|
|
75
75
|
}
|
|
76
76
|
// Graph Reading Operations
|
|
77
|
-
async readGraph() {
|
|
77
|
+
async readGraph(threadId) {
|
|
78
78
|
await this.ensureInitialized();
|
|
79
|
-
return GraphReader.readGraph(this.storage);
|
|
79
|
+
return GraphReader.readGraph(this.storage, threadId);
|
|
80
80
|
}
|
|
81
81
|
// Search Operations
|
|
82
|
-
async searchNodes(query) {
|
|
82
|
+
async searchNodes(threadId, query) {
|
|
83
83
|
await this.ensureInitialized();
|
|
84
|
-
return SearchService.searchNodes(this.storage, query);
|
|
84
|
+
return SearchService.searchNodes(this.storage, threadId, query);
|
|
85
85
|
}
|
|
86
|
-
async openNodes(names) {
|
|
86
|
+
async openNodes(threadId, names) {
|
|
87
87
|
await this.ensureInitialized();
|
|
88
|
-
return SearchService.openNodes(this.storage, names);
|
|
88
|
+
return SearchService.openNodes(this.storage, threadId, names);
|
|
89
89
|
}
|
|
90
|
-
async queryNodes(filters) {
|
|
90
|
+
async queryNodes(threadId, filters) {
|
|
91
91
|
await this.ensureInitialized();
|
|
92
|
-
return SearchService.queryNodes(this.storage, filters);
|
|
92
|
+
return SearchService.queryNodes(this.storage, threadId, filters);
|
|
93
93
|
}
|
|
94
94
|
// Entity Query Operations
|
|
95
95
|
async getAllEntityNames() {
|
|
@@ -105,26 +105,26 @@ export class KnowledgeGraphManager {
|
|
|
105
105
|
return EntityQueries.listEntities(this.storage, threadId, entityType, namePattern);
|
|
106
106
|
}
|
|
107
107
|
// Memory Statistics & Insights
|
|
108
|
-
async getMemoryStats() {
|
|
108
|
+
async getMemoryStats(threadId) {
|
|
109
109
|
await this.ensureInitialized();
|
|
110
|
-
return MemoryStats.getMemoryStats(this.storage);
|
|
110
|
+
return MemoryStats.getMemoryStats(this.storage, threadId);
|
|
111
111
|
}
|
|
112
|
-
async getRecentChanges(since) {
|
|
112
|
+
async getRecentChanges(threadId, since) {
|
|
113
113
|
await this.ensureInitialized();
|
|
114
|
-
return MemoryStats.getRecentChanges(this.storage, since);
|
|
114
|
+
return MemoryStats.getRecentChanges(this.storage, threadId, since);
|
|
115
115
|
}
|
|
116
116
|
// Analysis Operations
|
|
117
|
-
async findRelationPath(from, to, maxDepth = 5) {
|
|
117
|
+
async findRelationPath(threadId, from, to, maxDepth = 5) {
|
|
118
118
|
await this.ensureInitialized();
|
|
119
|
-
return PathFinder.findRelationPath(this.storage, from, to, maxDepth);
|
|
119
|
+
return PathFinder.findRelationPath(this.storage, threadId, from, to, maxDepth);
|
|
120
120
|
}
|
|
121
|
-
async detectConflicts() {
|
|
121
|
+
async detectConflicts(threadId) {
|
|
122
122
|
await this.ensureInitialized();
|
|
123
|
-
return ConflictDetector.detectConflicts(this.storage);
|
|
123
|
+
return ConflictDetector.detectConflicts(this.storage, threadId);
|
|
124
124
|
}
|
|
125
|
-
async getContext(entityNames, depth = 1) {
|
|
125
|
+
async getContext(threadId, entityNames, depth = 1) {
|
|
126
126
|
await this.ensureInitialized();
|
|
127
|
-
return ContextBuilder.getContext(this.storage, entityNames, depth);
|
|
127
|
+
return ContextBuilder.getContext(this.storage, threadId, entityNames, depth);
|
|
128
128
|
}
|
|
129
129
|
async getAnalytics(threadId) {
|
|
130
130
|
await this.ensureInitialized();
|
|
@@ -144,17 +144,17 @@ export class KnowledgeGraphManager {
|
|
|
144
144
|
await this.ensureInitialized();
|
|
145
145
|
return FlagManager.flagForReview(this.storage, entityName, reason, reviewer);
|
|
146
146
|
}
|
|
147
|
-
async getFlaggedEntities() {
|
|
147
|
+
async getFlaggedEntities(threadId) {
|
|
148
148
|
await this.ensureInitialized();
|
|
149
|
-
return FlagManager.getFlaggedEntities(this.storage);
|
|
149
|
+
return FlagManager.getFlaggedEntities(this.storage, threadId);
|
|
150
150
|
}
|
|
151
151
|
async listConversations() {
|
|
152
152
|
await this.ensureInitialized();
|
|
153
153
|
return ConversationService.listConversations(this.storage);
|
|
154
154
|
}
|
|
155
155
|
// Observation Versioning
|
|
156
|
-
async getObservationHistory(entityName, observationId) {
|
|
156
|
+
async getObservationHistory(threadId, entityName, observationId) {
|
|
157
157
|
await this.ensureInitialized();
|
|
158
|
-
return ObservationHistory.getObservationHistory(this.storage, entityName, observationId);
|
|
158
|
+
return ObservationHistory.getObservationHistory(this.storage, threadId, entityName, observationId);
|
|
159
159
|
}
|
|
160
160
|
}
|
|
@@ -2,55 +2,57 @@
|
|
|
2
2
|
* Entity query operations
|
|
3
3
|
*/
|
|
4
4
|
/**
|
|
5
|
-
* Get names of all entities
|
|
6
|
-
* @returns Set of entity names that exist in the graph.
|
|
5
|
+
* Get names of all entities across all threads.
|
|
7
6
|
*
|
|
8
|
-
* Note:
|
|
9
|
-
* all threads
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
7
|
+
* Note: This function is used for administrative purposes and returns entity names
|
|
8
|
+
* from all threads. Entity names are NOT globally unique - different threads can
|
|
9
|
+
* have entities with the same name. This function is primarily used by the manager's
|
|
10
|
+
* getAllEntityNames() method.
|
|
11
|
+
*
|
|
12
|
+
* For thread-isolated validation (e.g., in save_memory), use getEntityNamesInThread() instead.
|
|
13
13
|
*/
|
|
14
14
|
export async function getAllEntityNames(storage) {
|
|
15
15
|
const graph = await storage.loadGraph();
|
|
16
16
|
const entityNames = new Set();
|
|
17
|
-
// Return all
|
|
18
|
-
// This allows incremental building: entities from previous save_memory calls
|
|
19
|
-
// can be referenced in new calls, enabling cross-save entity relations
|
|
17
|
+
// Return all entity names from all threads
|
|
20
18
|
for (const entity of graph.entities) {
|
|
21
19
|
entityNames.add(entity.name);
|
|
22
20
|
}
|
|
23
21
|
return entityNames;
|
|
24
22
|
}
|
|
25
23
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* This method is kept for backward compatibility. It accepts a threadId parameter
|
|
29
|
-
* for API consistency but does not use it for filtering; it returns the same
|
|
30
|
-
* global set of entity names as {@link getAllEntityNames}.
|
|
24
|
+
* Get names of entities in a specific thread for thread isolation.
|
|
25
|
+
* This ensures entities can only reference other entities in the same thread.
|
|
31
26
|
*
|
|
32
27
|
* @param storage Storage adapter
|
|
33
|
-
* @param threadId The thread ID
|
|
34
|
-
* @returns Set of entity names that exist in the
|
|
28
|
+
* @param threadId The thread ID to filter by
|
|
29
|
+
* @returns Set of entity names that exist in the thread
|
|
35
30
|
*/
|
|
36
31
|
export async function getEntityNamesInThread(storage, threadId) {
|
|
37
|
-
|
|
32
|
+
const graph = await storage.loadGraph();
|
|
33
|
+
const entityNames = new Set();
|
|
34
|
+
// Return only entities in the specified thread
|
|
35
|
+
for (const entity of graph.entities) {
|
|
36
|
+
if (entity.agentThreadId === threadId) {
|
|
37
|
+
entityNames.add(entity.name);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return entityNames;
|
|
38
41
|
}
|
|
39
42
|
/**
|
|
40
43
|
* List entities with optional filtering by type and name pattern
|
|
44
|
+
* Thread isolation enforced - threadId is required.
|
|
45
|
+
*
|
|
41
46
|
* @param storage Storage adapter
|
|
42
|
-
* @param threadId
|
|
47
|
+
* @param threadId Thread ID to filter by (required for thread isolation)
|
|
43
48
|
* @param entityType Optional entity type filter (exact match)
|
|
44
49
|
* @param namePattern Optional name pattern filter (case-insensitive substring match)
|
|
45
50
|
* @returns Array of entities with name and entityType
|
|
46
51
|
*/
|
|
47
52
|
export async function listEntities(storage, threadId, entityType, namePattern) {
|
|
48
53
|
const graph = await storage.loadGraph();
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (threadId) {
|
|
52
|
-
filteredEntities = filteredEntities.filter(e => e.agentThreadId === threadId);
|
|
53
|
-
}
|
|
54
|
+
// Filter by thread ID (required for thread isolation)
|
|
55
|
+
let filteredEntities = graph.entities.filter(e => e.agentThreadId === threadId);
|
|
54
56
|
// Filter by entity type if specified
|
|
55
57
|
if (entityType) {
|
|
56
58
|
filteredEntities = filteredEntities.filter(e => e.entityType === entityType);
|
|
@@ -2,8 +2,19 @@
|
|
|
2
2
|
* Graph reading operations
|
|
3
3
|
*/
|
|
4
4
|
/**
|
|
5
|
-
* Read the
|
|
5
|
+
* Read the knowledge graph filtered by threadId for thread isolation
|
|
6
6
|
*/
|
|
7
|
-
export async function readGraph(storage) {
|
|
8
|
-
|
|
7
|
+
export async function readGraph(storage, threadId) {
|
|
8
|
+
const graph = await storage.loadGraph();
|
|
9
|
+
// Filter entities by threadId
|
|
10
|
+
const filteredEntities = graph.entities.filter(e => e.agentThreadId === threadId);
|
|
11
|
+
// Create a Set of filtered entity names for quick lookup
|
|
12
|
+
const filteredEntityNames = new Set(filteredEntities.map(e => e.name));
|
|
13
|
+
// Filter relations to only include those between filtered entities and from the same thread
|
|
14
|
+
const filteredRelations = graph.relations.filter(r => r.agentThreadId === threadId &&
|
|
15
|
+
filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to));
|
|
16
|
+
return {
|
|
17
|
+
entities: filteredEntities,
|
|
18
|
+
relations: filteredRelations
|
|
19
|
+
};
|
|
9
20
|
}
|
|
@@ -4,17 +4,20 @@
|
|
|
4
4
|
/**
|
|
5
5
|
* Search for nodes in the knowledge graph by query string
|
|
6
6
|
* Searches entity names, types, and observation content
|
|
7
|
+
* Filtered by threadId for thread isolation
|
|
7
8
|
*/
|
|
8
|
-
export async function searchNodes(storage, query) {
|
|
9
|
+
export async function searchNodes(storage, threadId, query) {
|
|
9
10
|
const graph = await storage.loadGraph();
|
|
10
|
-
// Filter entities
|
|
11
|
-
const filteredEntities = graph.entities.filter(e => e.
|
|
12
|
-
e.
|
|
13
|
-
|
|
11
|
+
// Filter entities by threadId first, then by search query
|
|
12
|
+
const filteredEntities = graph.entities.filter(e => e.agentThreadId === threadId &&
|
|
13
|
+
(e.name.toLowerCase().includes(query.toLowerCase()) ||
|
|
14
|
+
e.entityType.toLowerCase().includes(query.toLowerCase()) ||
|
|
15
|
+
e.observations.some(o => o.content?.toLowerCase().includes(query.toLowerCase()))));
|
|
14
16
|
// Create a Set of filtered entity names for quick lookup
|
|
15
17
|
const filteredEntityNames = new Set(filteredEntities.map(e => e.name));
|
|
16
|
-
// Filter relations to only include those between filtered entities
|
|
17
|
-
const filteredRelations = graph.relations.filter(r =>
|
|
18
|
+
// Filter relations to only include those between filtered entities and from the same thread
|
|
19
|
+
const filteredRelations = graph.relations.filter(r => r.agentThreadId === threadId &&
|
|
20
|
+
filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to));
|
|
18
21
|
const filteredGraph = {
|
|
19
22
|
entities: filteredEntities,
|
|
20
23
|
relations: filteredRelations,
|
|
@@ -24,15 +27,17 @@ export async function searchNodes(storage, query) {
|
|
|
24
27
|
/**
|
|
25
28
|
* Open specific nodes by name
|
|
26
29
|
* Returns a subgraph containing only the specified entities and relations between them
|
|
30
|
+
* Filtered by threadId for thread isolation
|
|
27
31
|
*/
|
|
28
|
-
export async function openNodes(storage, names) {
|
|
32
|
+
export async function openNodes(storage, threadId, names) {
|
|
29
33
|
const graph = await storage.loadGraph();
|
|
30
|
-
// Filter entities
|
|
31
|
-
const filteredEntities = graph.entities.filter(e => names.includes(e.name));
|
|
34
|
+
// Filter entities by threadId first, then by name
|
|
35
|
+
const filteredEntities = graph.entities.filter(e => e.agentThreadId === threadId && names.includes(e.name));
|
|
32
36
|
// Create a Set of filtered entity names for quick lookup
|
|
33
37
|
const filteredEntityNames = new Set(filteredEntities.map(e => e.name));
|
|
34
|
-
// Filter relations to only include those between filtered entities
|
|
35
|
-
const filteredRelations = graph.relations.filter(r =>
|
|
38
|
+
// Filter relations to only include those between filtered entities and from the same thread
|
|
39
|
+
const filteredRelations = graph.relations.filter(r => r.agentThreadId === threadId &&
|
|
40
|
+
filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to));
|
|
36
41
|
const filteredGraph = {
|
|
37
42
|
entities: filteredEntities,
|
|
38
43
|
relations: filteredRelations,
|
|
@@ -42,15 +47,18 @@ export async function openNodes(storage, names) {
|
|
|
42
47
|
/**
|
|
43
48
|
* Query nodes with advanced filters
|
|
44
49
|
* Supports filtering by timestamp range, confidence range, and importance range
|
|
50
|
+
* Filtered by threadId for thread isolation
|
|
45
51
|
*/
|
|
46
|
-
export async function queryNodes(storage, filters) {
|
|
52
|
+
export async function queryNodes(storage, threadId, filters) {
|
|
47
53
|
const graph = await storage.loadGraph();
|
|
48
|
-
//
|
|
49
|
-
if (!filters) {
|
|
50
|
-
return graph;
|
|
51
|
-
}
|
|
52
|
-
// Apply filters to entities
|
|
54
|
+
// Apply filters to entities, starting with threadId filter
|
|
53
55
|
const filteredEntities = graph.entities.filter(e => {
|
|
56
|
+
// Thread isolation filter - must match
|
|
57
|
+
if (e.agentThreadId !== threadId)
|
|
58
|
+
return false;
|
|
59
|
+
// Optional filters below
|
|
60
|
+
if (!filters)
|
|
61
|
+
return true;
|
|
54
62
|
// Timestamp range filter
|
|
55
63
|
if (filters.timestampStart && e.timestamp < filters.timestampStart)
|
|
56
64
|
return false;
|
|
@@ -72,9 +80,15 @@ export async function queryNodes(storage, filters) {
|
|
|
72
80
|
const filteredEntityNames = new Set(filteredEntities.map(e => e.name));
|
|
73
81
|
// Apply filters to relations (and ensure they connect filtered entities)
|
|
74
82
|
const filteredRelations = graph.relations.filter(r => {
|
|
83
|
+
// Thread isolation filter - must match
|
|
84
|
+
if (r.agentThreadId !== threadId)
|
|
85
|
+
return false;
|
|
75
86
|
// Must connect filtered entities
|
|
76
87
|
if (!filteredEntityNames.has(r.from) || !filteredEntityNames.has(r.to))
|
|
77
88
|
return false;
|
|
89
|
+
// Optional filters below
|
|
90
|
+
if (!filters)
|
|
91
|
+
return true;
|
|
78
92
|
// Timestamp range filter
|
|
79
93
|
if (filters.timestampStart && r.timestamp < filters.timestampStart)
|
|
80
94
|
return false;
|
package/dist/lib/schemas.js
CHANGED
|
@@ -50,8 +50,8 @@ export const SaveMemoryEntitySchema = z.object({
|
|
|
50
50
|
importance: z.number().min(0).max(1).optional().default(0.5).describe("Importance for memory integrity (0-1)")
|
|
51
51
|
});
|
|
52
52
|
export const SaveMemoryInputSchema = z.object({
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
threadId: z.string().min(1).describe("Thread ID for this conversation/project"),
|
|
54
|
+
entities: z.array(SaveMemoryEntitySchema).min(1).describe("Array of entities to save")
|
|
55
55
|
});
|
|
56
56
|
export const SaveMemoryOutputSchema = z.object({
|
|
57
57
|
success: z.boolean(),
|
|
@@ -95,6 +95,7 @@ export const GetAnalyticsOutputSchema = z.object({
|
|
|
95
95
|
});
|
|
96
96
|
// Schema for get_observation_history tool (Observation Versioning section of spec)
|
|
97
97
|
export const GetObservationHistoryInputSchema = z.object({
|
|
98
|
+
threadId: z.string().min(1).describe("Thread ID for this conversation/project"),
|
|
98
99
|
entityName: z.string().min(1).describe("Name of the entity"),
|
|
99
100
|
observationId: z.string().min(1).describe("ID of the observation to retrieve history for")
|
|
100
101
|
});
|
|
@@ -103,7 +104,7 @@ export const GetObservationHistoryOutputSchema = z.object({
|
|
|
103
104
|
});
|
|
104
105
|
// Schema for list_entities tool (Simple Entity Lookup)
|
|
105
106
|
export const ListEntitiesInputSchema = z.object({
|
|
106
|
-
threadId: z.string().
|
|
107
|
+
threadId: z.string().min(1).describe("Thread ID for this conversation/project"),
|
|
107
108
|
entityType: z.string().optional().describe("Filter by entity type (e.g., 'Person', 'Service', 'Document')"),
|
|
108
109
|
namePattern: z.string().optional().describe("Filter by name pattern (case-insensitive substring match)")
|
|
109
110
|
});
|
|
@@ -115,8 +116,8 @@ export const ListEntitiesOutputSchema = z.object({
|
|
|
115
116
|
});
|
|
116
117
|
// Schema for validate_memory tool (Pre-Validation)
|
|
117
118
|
export const ValidateMemoryInputSchema = z.object({
|
|
118
|
-
|
|
119
|
-
|
|
119
|
+
threadId: z.string().min(1).describe("Thread ID for this conversation/project"),
|
|
120
|
+
entities: z.array(SaveMemoryEntitySchema).min(1).describe("Array of entities to validate")
|
|
120
121
|
});
|
|
121
122
|
export const ValidateMemoryOutputSchema = z.object({
|
|
122
123
|
all_valid: z.boolean().describe("True if all entities pass validation"),
|
|
@@ -131,10 +132,10 @@ export const ValidateMemoryOutputSchema = z.object({
|
|
|
131
132
|
});
|
|
132
133
|
// Schema for update_observation tool
|
|
133
134
|
export const UpdateObservationInputSchema = z.object({
|
|
135
|
+
agentThreadId: z.string().min(1).describe("Agent thread ID making this update"),
|
|
134
136
|
entityName: z.string().min(1).describe("Name of the entity containing the observation"),
|
|
135
137
|
observationId: z.string().min(1).describe("ID of the observation to update"),
|
|
136
138
|
newContent: z.string().min(1).max(300).describe("New content for the observation (max 300 chars). Minimum 1 character to allow short but valid observations like abbreviations or single words."),
|
|
137
|
-
agentThreadId: z.string().min(1).describe("Agent thread ID making this update"),
|
|
138
139
|
timestamp: z.string().describe("ISO 8601 timestamp of the update"),
|
|
139
140
|
confidence: z.number().min(0).max(1).optional().describe("Optional confidence score (0-1), inherits from old observation if not provided"),
|
|
140
141
|
importance: z.number().min(0).max(1).optional().describe("Optional importance score (0-1), inherits from old observation if not provided")
|
|
@@ -144,3 +145,57 @@ export const UpdateObservationOutputSchema = z.object({
|
|
|
144
145
|
updatedObservation: ObservationSchema.describe("The new version of the observation"),
|
|
145
146
|
message: z.string()
|
|
146
147
|
});
|
|
148
|
+
// Schema for read_graph tool
|
|
149
|
+
export const ReadGraphInputSchema = z.object({
|
|
150
|
+
threadId: z.string().min(1).describe("Thread ID for this conversation/project")
|
|
151
|
+
});
|
|
152
|
+
// Schema for search_nodes tool
|
|
153
|
+
export const SearchNodesInputSchema = z.object({
|
|
154
|
+
threadId: z.string().min(1).describe("Thread ID for this conversation/project"),
|
|
155
|
+
query: z.string().min(1).describe("Search query string")
|
|
156
|
+
});
|
|
157
|
+
// Schema for open_nodes tool
|
|
158
|
+
export const OpenNodesInputSchema = z.object({
|
|
159
|
+
threadId: z.string().min(1).describe("Thread ID for this conversation/project"),
|
|
160
|
+
names: z.array(z.string()).min(1).describe("Array of entity names to open")
|
|
161
|
+
});
|
|
162
|
+
// Schema for query_nodes tool
|
|
163
|
+
export const QueryNodesInputSchema = z.object({
|
|
164
|
+
threadId: z.string().min(1).describe("Thread ID for this conversation/project"),
|
|
165
|
+
timestampStart: z.string().optional().describe("Filter by start timestamp (ISO 8601)"),
|
|
166
|
+
timestampEnd: z.string().optional().describe("Filter by end timestamp (ISO 8601)"),
|
|
167
|
+
confidenceMin: z.number().min(0).max(1).optional().describe("Filter by minimum confidence (0-1)"),
|
|
168
|
+
confidenceMax: z.number().min(0).max(1).optional().describe("Filter by maximum confidence (0-1)"),
|
|
169
|
+
importanceMin: z.number().min(0).max(1).optional().describe("Filter by minimum importance (0-1)"),
|
|
170
|
+
importanceMax: z.number().min(0).max(1).optional().describe("Filter by maximum importance (0-1)")
|
|
171
|
+
});
|
|
172
|
+
// Schema for get_memory_stats tool
|
|
173
|
+
export const GetMemoryStatsInputSchema = z.object({
|
|
174
|
+
threadId: z.string().min(1).describe("Thread ID for this conversation/project")
|
|
175
|
+
});
|
|
176
|
+
// Schema for get_recent_changes tool
|
|
177
|
+
export const GetRecentChangesInputSchema = z.object({
|
|
178
|
+
threadId: z.string().min(1).describe("Thread ID for this conversation/project"),
|
|
179
|
+
since: z.string().describe("ISO 8601 timestamp to get changes since")
|
|
180
|
+
});
|
|
181
|
+
// Schema for find_relation_path tool
|
|
182
|
+
export const FindRelationPathInputSchema = z.object({
|
|
183
|
+
threadId: z.string().min(1).describe("Thread ID for this conversation/project"),
|
|
184
|
+
from: z.string().min(1).describe("Source entity name"),
|
|
185
|
+
to: z.string().min(1).describe("Target entity name"),
|
|
186
|
+
maxDepth: z.number().int().min(1).optional().default(5).describe("Maximum path depth (default: 5)")
|
|
187
|
+
});
|
|
188
|
+
// Schema for detect_conflicts tool
|
|
189
|
+
export const DetectConflictsInputSchema = z.object({
|
|
190
|
+
threadId: z.string().min(1).describe("Thread ID for this conversation/project")
|
|
191
|
+
});
|
|
192
|
+
// Schema for get_flagged_entities tool
|
|
193
|
+
export const GetFlaggedEntitiesInputSchema = z.object({
|
|
194
|
+
threadId: z.string().min(1).describe("Thread ID for this conversation/project")
|
|
195
|
+
});
|
|
196
|
+
// Schema for get_context tool
|
|
197
|
+
export const GetContextInputSchema = z.object({
|
|
198
|
+
threadId: z.string().min(1).describe("Thread ID for this conversation/project"),
|
|
199
|
+
entityNames: z.array(z.string()).min(1).describe("Array of entity names to get context for"),
|
|
200
|
+
depth: z.number().int().min(1).optional().default(1).describe("Context depth (default: 1)")
|
|
201
|
+
});
|
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Observation history and versioning service
|
|
3
3
|
*/
|
|
4
|
-
import { findEntity, findObservation } from '../utils/entity-finder.js';
|
|
5
4
|
/**
|
|
6
5
|
* Get full history chain for an observation
|
|
7
6
|
* Traces backwards and forwards through the version chain
|
|
7
|
+
* Filtered by threadId for thread isolation
|
|
8
8
|
*/
|
|
9
|
-
export async function getObservationHistory(storage, entityName, observationId) {
|
|
9
|
+
export async function getObservationHistory(storage, threadId, entityName, observationId) {
|
|
10
10
|
const graph = await storage.loadGraph();
|
|
11
|
-
// Find the entity
|
|
12
|
-
const entity =
|
|
11
|
+
// Find the entity - only in the specified thread
|
|
12
|
+
const entity = graph.entities.find(e => e.name === entityName && e.agentThreadId === threadId);
|
|
13
|
+
if (!entity) {
|
|
14
|
+
throw new Error(`Entity '${entityName}' not found in thread '${threadId}'`);
|
|
15
|
+
}
|
|
13
16
|
// Find the starting observation
|
|
14
|
-
const startObs =
|
|
17
|
+
const startObs = entity.observations.find(o => o.id === observationId);
|
|
18
|
+
if (!startObs) {
|
|
19
|
+
throw new Error(`Observation '${observationId}' not found in entity '${entityName}'`);
|
|
20
|
+
}
|
|
15
21
|
// Build the version chain
|
|
16
22
|
const history = [];
|
|
17
23
|
// Trace backwards to find all predecessors
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "server-memory-enhanced",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Enhanced MCP server for memory with agent threading, timestamps, and confidence scoring",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"mcpName": "io.github.modelcontextprotocol/server-memory-enhanced",
|