server-memory-enhanced 2.3.1 → 2.3.2

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.
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Analytics service for thread-specific metrics
3
+ */
4
+ /**
5
+ * Calculate recent changes for a thread
6
+ */
7
+ function calculateRecentChanges(threadEntities) {
8
+ return threadEntities
9
+ .map(e => ({
10
+ entityName: e.name,
11
+ entityType: e.entityType,
12
+ lastModified: e.timestamp,
13
+ changeType: 'created' // Simplified: all are 'created' for now
14
+ }))
15
+ .sort((a, b) => b.lastModified.localeCompare(a.lastModified))
16
+ .slice(0, 10);
17
+ }
18
+ /**
19
+ * Calculate top important entities for a thread
20
+ */
21
+ function calculateTopImportant(threadEntities) {
22
+ return threadEntities
23
+ .map(e => ({
24
+ entityName: e.name,
25
+ entityType: e.entityType,
26
+ importance: e.importance,
27
+ observationCount: e.observations.length
28
+ }))
29
+ .sort((a, b) => b.importance - a.importance)
30
+ .slice(0, 10);
31
+ }
32
+ /**
33
+ * Calculate most connected entities for a thread
34
+ */
35
+ function calculateMostConnected(threadEntities, entityRelationCounts) {
36
+ return Array.from(entityRelationCounts.entries())
37
+ .map(([entityName, connectedSet]) => {
38
+ const entity = threadEntities.find(e => e.name === entityName);
39
+ return {
40
+ entityName,
41
+ entityType: entity.entityType,
42
+ relationCount: connectedSet.size,
43
+ connectedTo: Array.from(connectedSet)
44
+ };
45
+ })
46
+ .sort((a, b) => b.relationCount - a.relationCount)
47
+ .slice(0, 10);
48
+ }
49
+ /**
50
+ * Calculate orphaned entities for a thread
51
+ */
52
+ function calculateOrphanedEntities(threadEntities, threadRelations, entityRelationCounts) {
53
+ const orphaned_entities = [];
54
+ const allEntityNames = new Set(threadEntities.map(e => e.name));
55
+ for (const entity of threadEntities) {
56
+ const relationCount = entityRelationCounts.get(entity.name)?.size || 0;
57
+ if (relationCount === 0) {
58
+ orphaned_entities.push({
59
+ entityName: entity.name,
60
+ entityType: entity.entityType,
61
+ reason: 'no_relations'
62
+ });
63
+ }
64
+ else {
65
+ // Check for broken relations (pointing to non-existent entities)
66
+ const entityRelations = threadRelations.filter(r => r.from === entity.name || r.to === entity.name);
67
+ const hasBrokenRelation = entityRelations.some(r => !allEntityNames.has(r.from) || !allEntityNames.has(r.to));
68
+ if (hasBrokenRelation) {
69
+ orphaned_entities.push({
70
+ entityName: entity.name,
71
+ entityType: entity.entityType,
72
+ reason: 'broken_relation'
73
+ });
74
+ }
75
+ }
76
+ }
77
+ return orphaned_entities;
78
+ }
79
+ /**
80
+ * Get analytics for a specific thread (limited to 4 core metrics)
81
+ */
82
+ export async function getAnalytics(storage, threadId) {
83
+ const graph = await storage.loadGraph();
84
+ // Filter to thread-specific data
85
+ const threadEntities = graph.entities.filter(e => e.agentThreadId === threadId);
86
+ const threadRelations = graph.relations.filter(r => r.agentThreadId === threadId);
87
+ // Calculate all metrics
88
+ const recent_changes = calculateRecentChanges(threadEntities);
89
+ const top_important = calculateTopImportant(threadEntities);
90
+ // Build the relation counts map once for both most_connected and orphaned_entities
91
+ const entityRelationCounts = new Map();
92
+ for (const entity of threadEntities) {
93
+ entityRelationCounts.set(entity.name, new Set());
94
+ }
95
+ for (const relation of threadRelations) {
96
+ if (entityRelationCounts.has(relation.from)) {
97
+ entityRelationCounts.get(relation.from).add(relation.to);
98
+ }
99
+ if (entityRelationCounts.has(relation.to)) {
100
+ entityRelationCounts.get(relation.to).add(relation.from);
101
+ }
102
+ }
103
+ const most_connected = calculateMostConnected(threadEntities, entityRelationCounts);
104
+ const orphaned_entities = calculateOrphanedEntities(threadEntities, threadRelations, entityRelationCounts);
105
+ return {
106
+ recent_changes,
107
+ top_important,
108
+ most_connected,
109
+ orphaned_entities
110
+ };
111
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Conflict detection service
3
+ */
4
+ import { hasNegation, NEGATION_WORDS } from '../utils/negation-detector.js';
5
+ /**
6
+ * Detect conflicting observations within entities
7
+ * Identifies potential contradictions by checking for negation patterns
8
+ */
9
+ export async function detectConflicts(storage) {
10
+ const graph = await storage.loadGraph();
11
+ const conflicts = [];
12
+ for (const entity of graph.entities) {
13
+ const entityConflicts = [];
14
+ for (let i = 0; i < entity.observations.length; i++) {
15
+ for (let j = i + 1; j < entity.observations.length; j++) {
16
+ const obs1Content = entity.observations[i].content.toLowerCase();
17
+ const obs2Content = entity.observations[j].content.toLowerCase();
18
+ // Skip if observations are in the same version chain
19
+ if (entity.observations[i].supersedes === entity.observations[j].id ||
20
+ entity.observations[j].supersedes === entity.observations[i].id ||
21
+ entity.observations[i].superseded_by === entity.observations[j].id ||
22
+ entity.observations[j].superseded_by === entity.observations[i].id) {
23
+ continue;
24
+ }
25
+ // Check for negation patterns
26
+ const obs1HasNegation = hasNegation(obs1Content);
27
+ const obs2HasNegation = hasNegation(obs2Content);
28
+ // If one has negation and they share key words, might be a conflict
29
+ if (obs1HasNegation !== obs2HasNegation) {
30
+ const words1 = obs1Content.split(/\s+/).filter(w => w.length > 3);
31
+ const words2Set = new Set(obs2Content.split(/\s+/).filter(w => w.length > 3));
32
+ const commonWords = words1.filter(w => words2Set.has(w) && !NEGATION_WORDS.has(w));
33
+ if (commonWords.length >= 2) {
34
+ entityConflicts.push({
35
+ obs1: entity.observations[i].content,
36
+ obs2: entity.observations[j].content,
37
+ reason: 'Potential contradiction with negation'
38
+ });
39
+ }
40
+ }
41
+ }
42
+ }
43
+ if (entityConflicts.length > 0) {
44
+ conflicts.push({ entityName: entity.name, conflicts: entityConflicts });
45
+ }
46
+ }
47
+ return conflicts;
48
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Context builder service
3
+ */
4
+ /**
5
+ * Get context (entities related to specified entities up to a certain depth)
6
+ * Expands to include related entities up to specified depth
7
+ */
8
+ export async function getContext(storage, entityNames, depth = 1) {
9
+ const graph = await storage.loadGraph();
10
+ const contextEntityNames = new Set(entityNames);
11
+ // Expand to include related entities up to specified depth
12
+ for (let d = 0; d < depth; d++) {
13
+ const currentEntities = Array.from(contextEntityNames);
14
+ for (const entityName of currentEntities) {
15
+ // Find all relations involving this entity
16
+ const relatedRelations = graph.relations.filter(r => r.from === entityName || r.to === entityName);
17
+ // Add related entities
18
+ relatedRelations.forEach(r => {
19
+ contextEntityNames.add(r.from);
20
+ contextEntityNames.add(r.to);
21
+ });
22
+ }
23
+ }
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 => contextEntityNames.has(r.from) && contextEntityNames.has(r.to));
27
+ return {
28
+ entities: contextEntities,
29
+ relations: contextRelations
30
+ };
31
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Memory statistics service
3
+ */
4
+ /**
5
+ * Get comprehensive memory statistics
6
+ */
7
+ export async function getMemoryStats(storage) {
8
+ const graph = await storage.loadGraph();
9
+ // Count entity types
10
+ const entityTypes = {};
11
+ graph.entities.forEach(e => {
12
+ entityTypes[e.entityType] = (entityTypes[e.entityType] || 0) + 1;
13
+ });
14
+ // Calculate averages
15
+ const avgConfidence = graph.entities.length > 0
16
+ ? graph.entities.reduce((sum, e) => sum + e.confidence, 0) / graph.entities.length
17
+ : 0;
18
+ const avgImportance = graph.entities.length > 0
19
+ ? graph.entities.reduce((sum, e) => sum + e.importance, 0) / graph.entities.length
20
+ : 0;
21
+ // Count unique threads
22
+ const threads = new Set([
23
+ ...graph.entities.map(e => e.agentThreadId),
24
+ ...graph.relations.map(r => r.agentThreadId)
25
+ ]);
26
+ // Recent activity (last 7 days, grouped by day)
27
+ const now = new Date();
28
+ const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
29
+ const recentEntities = graph.entities.filter(e => new Date(e.timestamp) >= sevenDaysAgo);
30
+ // Group by day
31
+ const activityByDay = {};
32
+ recentEntities.forEach(e => {
33
+ const day = e.timestamp.substring(0, 10); // YYYY-MM-DD
34
+ activityByDay[day] = (activityByDay[day] || 0) + 1;
35
+ });
36
+ const recentActivity = Object.entries(activityByDay)
37
+ .map(([timestamp, entityCount]) => ({ timestamp, entityCount }))
38
+ .sort((a, b) => a.timestamp.localeCompare(b.timestamp));
39
+ return {
40
+ entityCount: graph.entities.length,
41
+ relationCount: graph.relations.length,
42
+ threadCount: threads.size,
43
+ entityTypes,
44
+ avgConfidence,
45
+ avgImportance,
46
+ recentActivity
47
+ };
48
+ }
49
+ /**
50
+ * Get recent changes since a specific timestamp
51
+ */
52
+ export async function getRecentChanges(storage, since) {
53
+ const graph = await storage.loadGraph();
54
+ const sinceDate = new Date(since);
55
+ // Only return entities and relations that were actually modified since the specified time
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
+ return {
60
+ entities: recentEntities,
61
+ relations: recentRelations
62
+ };
63
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Path finding service for the knowledge graph
3
+ */
4
+ /**
5
+ * Find the shortest path between two entities in the knowledge graph
6
+ * Uses BFS algorithm with bidirectional search
7
+ */
8
+ export async function findRelationPath(storage, from, to, maxDepth = 5) {
9
+ const graph = await storage.loadGraph();
10
+ if (from === to) {
11
+ return { found: true, path: [from], relations: [] };
12
+ }
13
+ // Build indexes for efficient relation lookup
14
+ const relationsFrom = new Map();
15
+ const relationsTo = new Map();
16
+ for (const rel of graph.relations) {
17
+ if (!relationsFrom.has(rel.from)) {
18
+ relationsFrom.set(rel.from, []);
19
+ }
20
+ relationsFrom.get(rel.from).push(rel);
21
+ if (!relationsTo.has(rel.to)) {
22
+ relationsTo.set(rel.to, []);
23
+ }
24
+ relationsTo.get(rel.to).push(rel);
25
+ }
26
+ // BFS to find shortest path
27
+ const queue = [
28
+ { entity: from, path: [from], relations: [] }
29
+ ];
30
+ const visited = new Set([from]);
31
+ while (queue.length > 0) {
32
+ const current = queue.shift();
33
+ if (current.path.length > maxDepth) {
34
+ continue;
35
+ }
36
+ // Find all relations connected to current entity (both outgoing and incoming for bidirectional search)
37
+ const outgoing = relationsFrom.get(current.entity) || [];
38
+ const incoming = relationsTo.get(current.entity) || [];
39
+ // Check outgoing relations
40
+ for (const rel of outgoing) {
41
+ if (rel.to === to) {
42
+ return {
43
+ found: true,
44
+ path: [...current.path, rel.to],
45
+ relations: [...current.relations, rel]
46
+ };
47
+ }
48
+ if (!visited.has(rel.to)) {
49
+ visited.add(rel.to);
50
+ queue.push({
51
+ entity: rel.to,
52
+ path: [...current.path, rel.to],
53
+ relations: [...current.relations, rel]
54
+ });
55
+ }
56
+ }
57
+ // Check incoming relations (traverse backwards)
58
+ for (const rel of incoming) {
59
+ if (rel.from === to) {
60
+ return {
61
+ found: true,
62
+ path: [...current.path, rel.from],
63
+ relations: [...current.relations, rel]
64
+ };
65
+ }
66
+ if (!visited.has(rel.from)) {
67
+ visited.add(rel.from);
68
+ queue.push({
69
+ entity: rel.from,
70
+ path: [...current.path, rel.from],
71
+ relations: [...current.relations, rel]
72
+ });
73
+ }
74
+ }
75
+ }
76
+ return { found: false, path: [], relations: [] };
77
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Conversation service (agent threads)
3
+ */
4
+ /**
5
+ * List all conversations (agent threads) with summary information
6
+ */
7
+ export async function listConversations(storage) {
8
+ const graph = await storage.loadGraph();
9
+ // Group data by agent thread
10
+ const threadMap = new Map();
11
+ // Collect entities by thread
12
+ for (const entity of graph.entities) {
13
+ if (!threadMap.has(entity.agentThreadId)) {
14
+ threadMap.set(entity.agentThreadId, { entities: [], relations: [], timestamps: [] });
15
+ }
16
+ const threadData = threadMap.get(entity.agentThreadId);
17
+ threadData.entities.push(entity);
18
+ threadData.timestamps.push(entity.timestamp);
19
+ }
20
+ // Collect relations by thread
21
+ for (const relation of graph.relations) {
22
+ if (!threadMap.has(relation.agentThreadId)) {
23
+ threadMap.set(relation.agentThreadId, { entities: [], relations: [], timestamps: [] });
24
+ }
25
+ const threadData = threadMap.get(relation.agentThreadId);
26
+ threadData.relations.push(relation);
27
+ threadData.timestamps.push(relation.timestamp);
28
+ }
29
+ // Build conversation summaries
30
+ const conversations = Array.from(threadMap.entries()).map(([agentThreadId, data]) => {
31
+ const timestamps = data.timestamps.sort((a, b) => a.localeCompare(b));
32
+ return {
33
+ agentThreadId,
34
+ entityCount: data.entities.length,
35
+ relationCount: data.relations.length,
36
+ firstCreated: timestamps[0] || '',
37
+ lastUpdated: timestamps[timestamps.length - 1] || ''
38
+ };
39
+ });
40
+ // Sort by last updated (most recent first)
41
+ conversations.sort((a, b) => b.lastUpdated.localeCompare(a.lastUpdated));
42
+ return { conversations };
43
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Collaboration features (flagging, review)
3
+ */
4
+ import { randomUUID } from 'crypto';
5
+ /**
6
+ * Flag an entity for review
7
+ */
8
+ export async function flagForReview(storage, entityName, reason, reviewer) {
9
+ const graph = await storage.loadGraph();
10
+ const entity = graph.entities.find(e => e.name === entityName);
11
+ if (!entity) {
12
+ throw new Error(`Entity with name ${entityName} not found`);
13
+ }
14
+ // Add a special observation to mark for review
15
+ const flagContent = `[FLAGGED FOR REVIEW: ${reason}${reviewer ? ` - Reviewer: ${reviewer}` : ''}]`;
16
+ // Check if this flag already exists (by content)
17
+ if (!entity.observations.some(o => o.content === flagContent)) {
18
+ const flagObservation = {
19
+ id: `obs_${randomUUID()}`,
20
+ content: flagContent,
21
+ timestamp: new Date().toISOString(),
22
+ version: 1,
23
+ agentThreadId: entity.agentThreadId,
24
+ confidence: 1.0, // Flag observations have full confidence
25
+ importance: 1.0 // Flag observations are highly important
26
+ };
27
+ entity.observations.push(flagObservation);
28
+ entity.timestamp = new Date().toISOString();
29
+ await storage.saveGraph(graph);
30
+ }
31
+ }
32
+ /**
33
+ * Get all entities flagged for review
34
+ */
35
+ export async function getFlaggedEntities(storage) {
36
+ const graph = await storage.loadGraph();
37
+ return graph.entities.filter(e => e.observations.some(obs => obs.content.includes('[FLAGGED FOR REVIEW:')));
38
+ }