UNPKG

@claude-vector/claude-tools

Version:

Claude integration tools for AI-powered development assistance

381 lines (323 loc) 9.82 kB
/** * Session Manager for Claude development tasks * Enhanced with context persistence and integration */ import { promises as fs } from 'fs'; import path from 'path'; import crypto from 'crypto'; import os from 'os'; export class SessionManager { constructor(config = {}) { this.sessionsDir = config.sessionsDir || path.join(process.cwd(), '.claude-sessions'); this.currentSessionFile = path.join(os.homedir(), '.claude-session'); this.maxSessions = config.maxSessions || 100; this.contextManager = null; // Will be set by dependency injection this.persistentContext = new Map(); // Store context across sessions } /** * Set context manager for enhanced functionality */ setContextManager(contextManager) { this.contextManager = contextManager; } /** * Start a new session with enhanced context */ async startSession(task, metadata = {}) { const session = { id: this.generateSessionId(), task, startTime: new Date().toISOString(), activities: [], context: [], metadata: { ...metadata, projectPath: process.cwd(), environment: process.env.NODE_ENV || 'development' }, status: 'active', contextState: null // Will store ContextManager state }; // Find related sessions for context const relatedSessions = await this.findRelatedSessions(task, 3); if (relatedSessions.length > 0) { session.metadata.relatedSessions = relatedSessions.map(s => s.id); } // Initialize context if ContextManager is available if (this.contextManager) { this.contextManager.setTaskDescription(task); // Add context from related sessions await this.loadRelatedContext(relatedSessions); session.contextState = this.contextManager.getStats(); } // Save as current session await this.saveCurrentSession(session); // Save to sessions directory await this.saveSession(session); return session; } /** * Get current session */ async getCurrentSession() { try { const data = await fs.readFile(this.currentSessionFile, 'utf-8'); return JSON.parse(data); } catch (error) { return null; } } /** * Add activity to current session */ async addActivity(type, data) { const session = await this.getCurrentSession(); if (!session) { throw new Error('No active session'); } const activity = { type, timestamp: new Date().toISOString(), data }; session.activities.push(activity); session.lastActivity = activity.timestamp; await this.saveCurrentSession(session); await this.saveSession(session); return activity; } /** * Complete current session */ async completeSession(summary = '') { const session = await this.getCurrentSession(); if (!session) { throw new Error('No active session'); } session.status = 'completed'; session.endTime = new Date().toISOString(); session.summary = summary; await this.saveSession(session); await this.clearCurrentSession(); return session; } /** * Get all sessions */ async getAllSessions() { try { await fs.mkdir(this.sessionsDir, { recursive: true }); const files = await fs.readdir(this.sessionsDir); const sessions = []; for (const file of files) { if (file.endsWith('.json')) { try { const data = await fs.readFile(path.join(this.sessionsDir, file), 'utf-8'); sessions.push(JSON.parse(data)); } catch (error) { // Skip invalid files } } } // Sort by start time (newest first) sessions.sort((a, b) => new Date(b.startTime) - new Date(a.startTime)); return sessions; } catch (error) { return []; } } /** * Get session by ID */ async getSession(sessionId) { try { const filePath = path.join(this.sessionsDir, `${sessionId}.json`); const data = await fs.readFile(filePath, 'utf-8'); return JSON.parse(data); } catch (error) { return null; } } /** * Find related sessions */ async findRelatedSessions(task, limit = 5) { const sessions = await this.getAllSessions(); const taskWords = task.toLowerCase().split(/\s+/); // Score sessions by keyword overlap const scored = sessions.map(session => { const sessionWords = session.task.toLowerCase().split(/\s+/); const overlap = taskWords.filter(word => sessionWords.includes(word)).length; return { session, score: overlap }; }); // Sort by score and return top matches scored.sort((a, b) => b.score - a.score); return scored .filter(item => item.score > 0) .slice(0, limit) .map(item => item.session); } /** * Clean up old sessions */ async cleanup(daysToKeep = 30) { const sessions = await this.getAllSessions(); const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - daysToKeep); let deleted = 0; for (const session of sessions) { const sessionDate = new Date(session.startTime); if (sessionDate < cutoffDate) { try { await fs.unlink(path.join(this.sessionsDir, `${session.id}.json`)); deleted++; } catch (error) { // Ignore errors } } } return { deleted, remaining: sessions.length - deleted }; } /** * Private: Generate session ID */ generateSessionId() { const timestamp = Date.now(); const random = crypto.randomBytes(4).toString('hex'); return `session-${timestamp}-${random}`; } /** * Private: Save current session */ async saveCurrentSession(session) { await fs.writeFile(this.currentSessionFile, JSON.stringify(session, null, 2)); } /** * Private: Save session to directory */ async saveSession(session) { await fs.mkdir(this.sessionsDir, { recursive: true }); const filePath = path.join(this.sessionsDir, `${session.id}.json`); await fs.writeFile(filePath, JSON.stringify(session, null, 2)); } /** * Load context from related sessions */ async loadRelatedContext(relatedSessions) { if (!this.contextManager) return; for (const session of relatedSessions) { // Add key activities as context const relevantActivities = session.activities .filter(activity => activity.type === 'solution' || activity.type === 'error_fix') .slice(-3); // Last 3 relevant activities for (const activity of relevantActivities) { this.contextManager.addItem({ type: 'context', content: `Previous session "${session.task}": ${JSON.stringify(activity.data)}`, metadata: { sessionId: session.id, activityType: activity.type, timestamp: activity.timestamp } }, 'medium'); } } } /** * Add context item to current session */ async addContext(item, priority = 'medium') { const session = await this.getCurrentSession(); if (!session) { throw new Error('No active session'); } // Store in session session.context.push({ ...item, timestamp: new Date().toISOString(), priority }); // Add to ContextManager if available if (this.contextManager) { this.contextManager.addItem(item, priority); session.contextState = this.contextManager.getStats(); } await this.saveCurrentSession(session); await this.saveSession(session); return item; } /** * Get formatted context for Claude */ async getFormattedContext() { if (this.contextManager) { return this.contextManager.getFormattedContext(); } const session = await this.getCurrentSession(); if (!session || session.context.length === 0) { return 'No context available.'; } let formatted = `# Current Task\n${session.task}\n\n`; formatted += '# Session Context\n\n'; for (const item of session.context) { formatted += `## ${item.type || 'Information'}\n`; formatted += `${item.content}\n\n`; } return formatted; } /** * Update session with context state */ async updateContextState() { const session = await this.getCurrentSession(); if (!session || !this.contextManager) return; session.contextState = this.contextManager.getStats(); await this.saveCurrentSession(session); await this.saveSession(session); } /** * Get session statistics */ async getSessionStats() { const session = await this.getCurrentSession(); if (!session) return null; const duration = session.endTime ? new Date(session.endTime) - new Date(session.startTime) : Date.now() - new Date(session.startTime); return { id: session.id, task: session.task, status: session.status, duration: Math.round(duration / 1000), // seconds activitiesCount: session.activities.length, contextItemsCount: session.context.length, contextState: session.contextState, startTime: session.startTime, lastActivity: session.lastActivity }; } /** * Export session data */ async exportSession(sessionId = null) { const session = sessionId ? await this.getSession(sessionId) : await this.getCurrentSession(); if (!session) return null; return { ...session, exportTime: new Date().toISOString(), formattedContext: await this.getFormattedContext() }; } /** * Private: Clear current session */ async clearCurrentSession() { try { await fs.unlink(this.currentSessionFile); } catch (error) { // File might not exist } } }