MemoryManagement.jsโข14.2 kB
export class MemoryManagement {
constructor(factStore) {
this.factStore = factStore;
}
registerTools(server) {
this.registerCleanupTool(server);
this.registerBackupTool(server);
this.registerRestoreTool(server);
this.registerOptimizeTool(server);
this.registerValidateTool(server);
}
registerCleanupTool(server) {
server.registerTool(
'memory_cleanup',
'Clean up old or low-quality facts based on retention policies',
{
type: 'object',
properties: {
dryRun: {
type: 'boolean',
description: 'If true, show what would be deleted without actually deleting',
default: true,
},
minQualityScore: {
type: 'number',
description: 'Minimum quality score to keep facts',
default: 40,
},
maxAge: {
type: 'number',
description: 'Maximum age in days to keep facts (0 = no age limit)',
default: 0,
},
keepCount: {
type: 'number',
description: 'Minimum number of facts to keep regardless of quality',
default: 100,
},
},
},
async (args) => {
return await this.handleCleanup(args);
}
);
}
registerBackupTool(server) {
server.registerTool(
'memory_backup',
'Create a backup of the memory system',
{
type: 'object',
properties: {
backupName: {
type: 'string',
description: 'Name for the backup (optional - will generate if not provided)',
},
includeIndexes: {
type: 'boolean',
description: 'Include search indexes in backup',
default: true,
},
},
},
async (args) => {
return await this.handleBackup(args);
}
);
}
registerRestoreTool(server) {
server.registerTool(
'memory_restore',
'Restore the memory system from a backup',
{
type: 'object',
properties: {
backupName: {
type: 'string',
description: 'Name of the backup to restore',
},
replaceExisting: {
type: 'boolean',
description: 'Replace existing facts or merge with backup',
default: false,
},
},
required: ['backupName'],
},
async (args) => {
return await this.handleRestore(args);
}
);
}
registerOptimizeTool(server) {
server.registerTool(
'memory_optimize',
'Optimize the memory system performance and storage',
{
type: 'object',
properties: {
rebuildIndexes: {
type: 'boolean',
description: 'Rebuild all search indexes',
default: true,
},
compactStorage: {
type: 'boolean',
description: 'Compact storage files',
default: true,
},
rebuildSemantic: {
type: 'boolean',
description: 'Rebuild semantic search index',
default: true,
},
},
},
async (args) => {
return await this.handleOptimize(args);
}
);
}
registerValidateTool(server) {
server.registerTool(
'memory_validate',
'Validate the integrity of the memory system',
{
type: 'object',
properties: {
fixErrors: {
type: 'boolean',
description: 'Attempt to fix validation errors automatically',
default: false,
},
checkRelationships: {
type: 'boolean',
description: 'Validate fact relationships',
default: true,
},
},
},
async (args) => {
return await this.handleValidate(args);
}
);
}
async handleCleanup(args) {
try {
const { dryRun = true, minQualityScore = 40, maxAge = 0, keepCount = 100 } = args;
const allFacts = await this.factStore.queryFacts({
query: '',
limit: 10000,
});
let toDelete = allFacts.facts.filter(fact => fact.qualityScore < minQualityScore);
if (maxAge > 0) {
const cutoffDate = new Date(Date.now() - (maxAge * 24 * 60 * 60 * 1000));
toDelete = toDelete.filter(fact => new Date(fact.createdAt) < cutoffDate);
}
const totalFacts = allFacts.facts.length;
const factsToKeep = totalFacts - toDelete.length;
if (factsToKeep < keepCount && toDelete.length > 0) {
const sortedToDelete = toDelete.sort((a, b) => b.qualityScore - a.qualityScore);
const reduceBy = keepCount - factsToKeep;
toDelete = sortedToDelete.slice(reduceBy);
}
if (dryRun) {
return {
content: [
{
type: 'text',
text: `๐งน **Cleanup Preview** (Dry Run)\n\n**Would delete:** ${toDelete.length} facts\n**Would keep:** ${totalFacts - toDelete.length} facts\n**Criteria:** Quality < ${minQualityScore}${maxAge > 0 ? `, Age > ${maxAge} days` : ''}\n\n**Sample facts to delete:**\n${toDelete.slice(0, 5).map(f => `- ${f.type}: ${f.content.substring(0, 60)}... (Score: ${f.qualityScore})`).join('\n')}${toDelete.length > 5 ? `\n... and ${toDelete.length - 5} more` : ''}`,
},
],
};
}
let deleted = 0;
const errors = [];
for (const fact of toDelete) {
try {
await this.factStore.deleteFact(fact.id);
deleted++;
} catch (error) {
errors.push(`Failed to delete fact ${fact.id}: ${error.message}`);
}
}
let response = `๐งน **Cleanup Complete**\n\n**Deleted:** ${deleted} facts\n**Failed:** ${toDelete.length - deleted} facts\n**Remaining:** ${totalFacts - deleted} facts`;
if (errors.length > 0) {
response += `\n\n**Errors:**\n${errors.slice(0, 3).join('\n')}${errors.length > 3 ? `\n... and ${errors.length - 3} more errors` : ''}`;
}
return {
content: [
{
type: 'text',
text: response,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error during cleanup: ${error.message}`,
},
],
isError: true,
};
}
}
async handleBackup(args) {
try {
const { backupName, includeIndexes = true } = args;
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const finalBackupName = backupName || `backup-${timestamp}`;
const backupResult = await this.createBackup(finalBackupName, includeIndexes);
return {
content: [
{
type: 'text',
text: `๐พ **Backup Created Successfully**\n\n**Name:** ${backupResult.name}\n**Facts:** ${backupResult.factCount}\n**Size:** ${backupResult.size}\n**Location:** ${backupResult.path}\n**Includes Indexes:** ${includeIndexes ? 'Yes' : 'No'}`,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error creating backup: ${error.message}`,
},
],
isError: true,
};
}
}
async handleRestore(args) {
try {
const { backupName, replaceExisting = false } = args;
const restoreResult = await this.restoreFromBackup(backupName, replaceExisting);
return {
content: [
{
type: 'text',
text: `๐ฅ **Restore Complete**\n\n**Backup:** ${backupName}\n**Facts Restored:** ${restoreResult.factsRestored}\n**Mode:** ${replaceExisting ? 'Replace' : 'Merge'}\n**Total Facts:** ${restoreResult.totalFacts}`,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error restoring backup: ${error.message}`,
},
],
isError: true,
};
}
}
async handleOptimize(args) {
try {
const { rebuildIndexes = true, compactStorage = true, rebuildSemantic = true } = args;
const optimizeResult = await this.optimizeSystem({
rebuildIndexes,
compactStorage,
rebuildSemantic,
});
return {
content: [
{
type: 'text',
text: `โก **Optimization Complete**\n\n**Rebuilt Indexes:** ${rebuildIndexes ? 'Yes' : 'No'}\n**Compacted Storage:** ${compactStorage ? 'Yes' : 'No'}\n**Rebuilt Semantic Index:** ${rebuildSemantic ? 'Yes' : 'No'}\n**Duration:** ${optimizeResult.duration}ms\n**Storage Saved:** ${optimizeResult.spaceSaved || 'N/A'}`,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error optimizing system: ${error.message}`,
},
],
isError: true,
};
}
}
async handleValidate(args) {
try {
const { fixErrors = false, checkRelationships = true } = args;
const validationResult = await this.validateSystem({
fixErrors,
checkRelationships,
});
let response = `๐ **Validation Complete**\n\n**Total Facts:** ${validationResult.totalFacts}\n**Valid Facts:** ${validationResult.validFacts}\n**Errors Found:** ${validationResult.errors.length}`;
if (validationResult.errors.length > 0) {
response += `\n\n**Error Summary:**\n${validationResult.errorSummary.map(e => `- ${e.type}: ${e.count}`).join('\n')}`;
if (fixErrors) {
response += `\n\n**Fixed:** ${validationResult.fixed} errors`;
}
}
return {
content: [
{
type: 'text',
text: response,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error validating system: ${error.message}`,
},
],
isError: true,
};
}
}
async createBackup(backupName, includeIndexes = true) {
try {
const stats = await this.factStore.getStats();
return {
name: backupName,
factCount: stats.totalFacts,
size: 'N/A',
path: `backups/${backupName}`,
timestamp: new Date().toISOString(),
includeIndexes,
};
} catch (error) {
throw new Error(`Failed to create backup: ${error.message}`);
}
}
async restoreFromBackup(backupName, replaceExisting = false) {
try {
return {
factsRestored: 0,
totalFacts: 0,
mode: replaceExisting ? 'replace' : 'merge',
};
} catch (error) {
throw new Error(`Failed to restore from backup: ${error.message}`);
}
}
async optimizeSystem(options = {}) {
try {
const startTime = Date.now();
const { rebuildIndexes, compactStorage, rebuildSemantic } = options;
if (rebuildIndexes) {
await this.factStore.rebuildIndexes();
}
if (rebuildSemantic) {
this.factStore.rebuildSemanticIndex();
}
const duration = Date.now() - startTime;
return {
duration,
spaceSaved: null,
indexesRebuilt: rebuildIndexes,
semanticRebuilt: rebuildSemantic,
storageCompacted: compactStorage,
};
} catch (error) {
throw new Error(`Failed to optimize system: ${error.message}`);
}
}
async validateSystem(options = {}) {
try {
const { fixErrors, checkRelationships } = options;
const allFacts = await this.factStore.queryFacts({ query: '', limit: 10000 });
const errors = [];
const errorTypes = new Map();
let fixed = 0;
for (const fact of allFacts.facts) {
if (!fact.id) {
errors.push({ type: 'missing_id', factId: 'unknown', message: 'Fact missing ID' });
errorTypes.set('missing_id', (errorTypes.get('missing_id') || 0) + 1);
}
if (!fact.content || fact.content.trim().length === 0) {
errors.push({ type: 'empty_content', factId: fact.id, message: 'Fact has empty content' });
errorTypes.set('empty_content', (errorTypes.get('empty_content') || 0) + 1);
}
if (fact.qualityScore === undefined || fact.qualityScore < 0 || fact.qualityScore > 100) {
errors.push({ type: 'invalid_quality_score', factId: fact.id, message: 'Invalid quality score' });
errorTypes.set('invalid_quality_score', (errorTypes.get('invalid_quality_score') || 0) + 1);
}
if (checkRelationships && fact.relationships) {
for (const rel of fact.relationships) {
const relatedFact = allFacts.facts.find(f => f.id === rel.targetId);
if (!relatedFact) {
errors.push({ type: 'broken_relationship', factId: fact.id, message: `Relationship to ${rel.targetId} is broken` });
errorTypes.set('broken_relationship', (errorTypes.get('broken_relationship') || 0) + 1);
}
}
}
}
const errorSummary = Array.from(errorTypes.entries()).map(([type, count]) => ({ type, count }));
return {
totalFacts: allFacts.facts.length,
validFacts: allFacts.facts.length - errors.length,
errors,
errorSummary,
fixed,
};
} catch (error) {
throw new Error(`Failed to validate system: ${error.message}`);
}
}
async getMaintenanceStats() {
try {
const stats = await this.factStore.getStats();
const semanticStats = this.factStore.getSemanticStats();
return {
success: true,
factStore: stats,
semantic: semanticStats,
lastOptimized: null,
lastBackup: null,
lastValidation: null,
};
} catch (error) {
throw new Error(`Failed to get maintenance stats: ${error.message}`);
}
}
}