contaigents
Version:
Modular AI Content Ecosystem with Audio Generation
195 lines (194 loc) • 6.41 kB
JavaScript
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;
}
}
}