UNPKG

contaigents

Version:

Modular AI Content Ecosystem with Audio Generation

195 lines (194 loc) 6.41 kB
import { FileService } from '../services/fileService.js'; import crypto from 'crypto'; /** * Manages conversation state and history for the agentic CLI * Handles multi-turn conversations, context preservation, and state persistence */ export class ConversationManager { constructor() { this.conversationsDir = './.contaigents/conversations'; this.activeConversation = null; this.fileService = new FileService(); } /** * Start a new conversation */ async startConversation(initialMessage, context, task) { const conversation = { id: crypto.randomUUID(), messages: [ { id: crypto.randomUUID(), role: 'user', content: initialMessage, timestamp: Date.now() } ], context, currentTask: task, status: 'active', createdAt: Date.now(), updatedAt: Date.now() }; this.activeConversation = conversation; await this.saveConversation(conversation); return conversation; } /** * Add a message to the current conversation */ async addMessage(role, content, toolCalls, metadata) { if (!this.activeConversation) { throw new Error('No active conversation. Start a conversation first.'); } const message = { id: crypto.randomUUID(), role, content, timestamp: Date.now(), toolCalls, metadata }; this.activeConversation.messages.push(message); this.activeConversation.updatedAt = Date.now(); await this.saveConversation(this.activeConversation); return message; } /** * Get the current active conversation */ getActiveConversation() { return this.activeConversation; } /** * Load a conversation by ID */ async loadConversation(conversationId) { try { const filePath = `${this.conversationsDir}/${conversationId}.json`; const content = await this.fileService.readFile(filePath); const conversation = JSON.parse(content); this.activeConversation = conversation; return conversation; } catch (error) { console.error(`Failed to load conversation ${conversationId}:`, error); return null; } } /** * Save conversation to disk */ async saveConversation(conversation) { try { const filePath = `${this.conversationsDir}/${conversation.id}.json`; await this.fileService.saveFile({ path: filePath, content: JSON.stringify(conversation, null, 2) }); } catch (error) { console.error('Failed to save conversation:', error); } } /** * List all conversations */ async listConversations() { try { const tree = await this.fileService.getFileTree(); const conversationFiles = this.findConversationFiles(tree); const conversations = []; for (const file of conversationFiles) { try { const content = await this.fileService.readFile(file.path); const conversation = JSON.parse(content); conversations.push(conversation); } catch (error) { console.error(`Failed to load conversation from ${file.path}:`, error); } } return conversations.sort((a, b) => b.updatedAt - a.updatedAt); } catch (error) { console.error('Failed to list conversations:', error); return []; } } /** * Find conversation files in the file tree */ findConversationFiles(tree) { const files = []; const traverse = (node) => { if (node.type === 'file' && node.path.includes('.contaigents/conversations') && node.path.endsWith('.json')) { files.push({ path: node.path }); } if (node.children) { node.children.forEach(traverse); } }; if (Array.isArray(tree)) { tree.forEach(traverse); } else { traverse(tree); } return files; } /** * Update conversation status */ async updateStatus(status) { if (!this.activeConversation) { throw new Error('No active conversation'); } this.activeConversation.status = status; this.activeConversation.updatedAt = Date.now(); await this.saveConversation(this.activeConversation); } /** * Update conversation context */ async updateContext(context) { if (!this.activeConversation) { throw new Error('No active conversation'); } this.activeConversation.context = { ...this.activeConversation.context, ...context }; this.activeConversation.updatedAt = Date.now(); await this.saveConversation(this.activeConversation); } /** * Get conversation summary for display */ getConversationSummary(conversation) { const firstMessage = conversation.messages.find(m => m.role === 'user')?.content || 'No messages'; const messageCount = conversation.messages.length; const lastUpdate = new Date(conversation.updatedAt).toLocaleString(); return `${firstMessage.substring(0, 50)}... (${messageCount} messages, updated: ${lastUpdate})`; } /** * Clear conversation history (keep only system messages) */ async clearHistory() { if (!this.activeConversation) { return; } this.activeConversation.messages = this.activeConversation.messages.filter(m => m.role === 'system'); this.activeConversation.updatedAt = Date.now(); await this.saveConversation(this.activeConversation); } /** * End the current conversation */ async endConversation() { if (this.activeConversation) { await this.updateStatus('completed'); this.activeConversation = null; } } }