claude-expert-workflow-mcp
Version:
Production-ready MCP server for AI-powered product development consultation through specialized expert roles. Enterprise-grade with memory management, monitoring, and Claude Code integration.
239 lines (196 loc) • 7.24 kB
text/typescript
import { ConversationState, ConversationMessage } from '@/types';
import { IPersistentStorage } from './interfaces';
import { logger } from '@/utils/logger';
/**
* Persistent conversation manager that extends the basic conversation manager
* with file-based persistence capabilities
*/
export class PersistentConversationManager {
private conversations: Map<string, ConversationState> = new Map();
private storage: IPersistentStorage;
private autoSave: boolean;
constructor(storage: IPersistentStorage, autoSave: boolean = true) {
this.storage = storage;
this.autoSave = autoSave;
}
/**
* Initialize by loading all conversations from storage
*/
async initialize(): Promise<void> {
try {
const conversationIds = await this.storage.listConversations();
for (const id of conversationIds) {
const conversation = await this.storage.loadConversation(id);
if (conversation) {
this.conversations.set(id, conversation);
}
}
logger.info(`Loaded ${conversationIds.length} conversations from storage`);
} catch (error) {
logger.error('Failed to initialize conversation manager:', error);
throw error;
}
}
generateConversationId(): string {
return `conv_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
async createConversation(id?: string): Promise<string> {
const conversationId = id || this.generateConversationId();
const conversation: ConversationState = {
id: conversationId,
messages: [],
completedTopics: [],
createdAt: new Date(),
updatedAt: new Date()
};
this.conversations.set(conversationId, conversation);
if (this.autoSave) {
await this.storage.saveConversation(conversation);
}
logger.debug(`Created conversation: ${conversationId}`);
return conversationId;
}
getConversation(id: string): ConversationState | undefined {
return this.conversations.get(id);
}
async loadConversation(id: string): Promise<ConversationState | undefined> {
// First check in-memory cache
const cached = this.conversations.get(id);
if (cached) {
return cached;
}
// Load from storage
try {
const conversation = await this.storage.loadConversation(id);
if (conversation) {
this.conversations.set(id, conversation);
return conversation;
}
return undefined;
} catch (error) {
logger.error(`Failed to load conversation ${id}:`, error);
return undefined;
}
}
async addMessage(conversationId: string, role: 'user' | 'assistant', content: string): Promise<void> {
let conversation = this.conversations.get(conversationId);
// Try to load from storage if not in memory
if (!conversation) {
conversation = await this.loadConversation(conversationId);
if (!conversation) {
throw new Error(`Conversation ${conversationId} not found`);
}
}
const message: ConversationMessage = {
role,
content,
timestamp: new Date()
};
conversation.messages.push(message);
conversation.updatedAt = new Date();
if (this.autoSave) {
await this.storage.saveConversation(conversation);
}
logger.debug(`Added message to conversation ${conversationId}: ${role}`);
}
getConversationHistory(conversationId: string): ConversationMessage[] {
const conversation = this.conversations.get(conversationId);
return conversation ? conversation.messages : [];
}
async updateTopic(conversationId: string, topic: string): Promise<void> {
let conversation = this.conversations.get(conversationId);
// Try to load from storage if not in memory
if (!conversation) {
conversation = await this.loadConversation(conversationId);
if (!conversation) {
throw new Error(`Conversation ${conversationId} not found`);
}
}
conversation.currentTopic = topic;
conversation.updatedAt = new Date();
if (this.autoSave) {
await this.storage.saveConversation(conversation);
}
}
async markTopicComplete(conversationId: string, topic: string): Promise<void> {
let conversation = this.conversations.get(conversationId);
// Try to load from storage if not in memory
if (!conversation) {
conversation = await this.loadConversation(conversationId);
if (!conversation) {
throw new Error(`Conversation ${conversationId} not found`);
}
}
if (!conversation.completedTopics.includes(topic)) {
conversation.completedTopics.push(topic);
conversation.updatedAt = new Date();
if (this.autoSave) {
await this.storage.saveConversation(conversation);
}
logger.debug(`Marked topic complete: ${topic} for conversation ${conversationId}`);
}
}
getCompletedTopics(conversationId: string): string[] {
const conversation = this.conversations.get(conversationId);
return conversation ? conversation.completedTopics : [];
}
async deleteConversation(conversationId: string): Promise<boolean> {
this.conversations.delete(conversationId);
try {
return await this.storage.deleteConversation(conversationId);
} catch (error) {
logger.error(`Failed to delete conversation ${conversationId}:`, error);
return false;
}
}
async listConversations(): Promise<string[]> {
try {
return await this.storage.listConversations();
} catch (error) {
logger.error('Failed to list conversations:', error);
return [];
}
}
async saveConversation(conversationId: string): Promise<void> {
const conversation = this.conversations.get(conversationId);
if (conversation) {
await this.storage.saveConversation(conversation);
logger.debug(`Manually saved conversation: ${conversationId}`);
} else {
throw new Error(`Conversation ${conversationId} not found in memory`);
}
}
async saveAllConversations(): Promise<void> {
const savePromises = Array.from(this.conversations.values()).map(conversation =>
this.storage.saveConversation(conversation)
);
await Promise.all(savePromises);
logger.info(`Saved ${savePromises.length} conversations to storage`);
}
async getConversationStats(): Promise<{
total: number;
inMemory: number;
averageMessages: number;
totalMessages: number;
}> {
const storedCount = (await this.storage.listConversations()).length;
const inMemoryCount = this.conversations.size;
let totalMessages = 0;
let conversationCount = 0;
for (const conversation of this.conversations.values()) {
totalMessages += conversation.messages.length;
conversationCount++;
}
const averageMessages = conversationCount > 0 ? totalMessages / conversationCount : 0;
return {
total: storedCount,
inMemory: inMemoryCount,
averageMessages: Math.round(averageMessages * 100) / 100,
totalMessages
};
}
setAutoSave(enabled: boolean): void {
this.autoSave = enabled;
logger.debug(`Auto-save ${enabled ? 'enabled' : 'disabled'}`);
}
}