bc-code-intelligence-mcp
Version:
BC Code Intelligence MCP Server - Complete Specialist Bundle with AI-driven expert consultation, seamless handoffs, and context-preserving workflows
255 lines • 9.18 kB
JavaScript
/**
* Specialist Session Manager
*
* Manages persistent, contextual conversations with BC specialist personas.
* Supports in-memory sessions by default with layer-configurable persistence.
*/
import { randomUUID } from 'crypto';
import { InMemorySessionStorage } from './session-storage/in-memory-storage.js';
import { FileSessionStorage } from './session-storage/file-storage.js';
export class SpecialistSessionManager {
storage;
layerService;
events = {};
constructor(layerService, storageConfig) {
this.layerService = layerService;
// Initialize storage based on configuration
this.storage = this.initializeStorage(storageConfig);
}
/**
* Start a new specialist session
*/
async startSession(specialistId, userId, initialMessage) {
// Validate specialist exists
const specialists = await this.layerService.getAllSpecialists();
const specialist = specialists.find(s => s.specialist_id === specialistId);
if (!specialist) {
throw new Error(`Specialist '${specialistId}' not found`);
}
const sessionId = randomUUID();
const now = new Date();
const session = {
sessionId,
specialistId,
userId,
startTime: now,
lastActivity: now,
status: 'active',
messages: [],
messageCount: 0,
context: {
solutions: [],
recommendations: [],
nextSteps: [],
userPreferences: {}
}
};
// Add initial message if provided
if (initialMessage) {
const initialMsg = {
id: randomUUID(),
timestamp: now,
type: 'user',
content: initialMessage
};
session.messages.push(initialMsg);
session.messageCount = 1;
}
await this.storage.createSession(session);
// Emit event
this.events.sessionStarted?.(session);
return session;
}
/**
* Get existing session
*/
async getSession(sessionId) {
return await this.storage.getSession(sessionId);
}
/**
* Continue existing session with new message
*/
async continueSession(sessionId, message) {
const session = await this.storage.getSession(sessionId);
if (!session) {
throw new Error(`Session '${sessionId}' not found`);
}
// Add user message
const userMessage = {
id: randomUUID(),
timestamp: new Date(),
type: 'user',
content: message
};
await this.addMessage(sessionId, userMessage);
return await this.storage.getSession(sessionId);
}
/**
* Transfer session to different specialist
*/
async transferSession(sessionId, toSpecialistId) {
const session = await this.storage.getSession(sessionId);
if (!session) {
throw new Error(`Session '${sessionId}' not found`);
}
// Validate target specialist exists
const specialists = await this.layerService.getAllSpecialists();
const targetSpecialist = specialists.find(s => s.specialist_id === toSpecialistId);
if (!targetSpecialist) {
throw new Error(`Target specialist '${toSpecialistId}' not found`);
}
const fromSpecialistId = session.specialistId;
// Update session
session.transferredFrom = session.specialistId;
session.specialistId = toSpecialistId;
session.transferredTo = toSpecialistId;
session.lastActivity = new Date();
// Add transfer message
const transferMessage = {
id: randomUUID(),
timestamp: new Date(),
type: 'specialist',
specialistId: 'system',
content: `Session transferred from ${fromSpecialistId} to ${toSpecialistId}`
};
session.messages.push(transferMessage);
session.messageCount++;
await this.storage.updateSession(session);
// Emit event
this.events.sessionTransferred?.(sessionId, fromSpecialistId, toSpecialistId);
return session;
}
/**
* End session
*/
async endSession(sessionId) {
const session = await this.storage.getSession(sessionId);
if (!session) {
throw new Error(`Session '${sessionId}' not found`);
}
session.status = 'completed';
session.lastActivity = new Date();
await this.storage.updateSession(session);
// Emit event
this.events.sessionEnded?.(sessionId);
}
/**
* List user's sessions
*/
async listUserSessions(userId) {
return await this.storage.getUserSessions(userId);
}
/**
* List user's active sessions
*/
async listActiveSessions(userId) {
return await this.storage.getActiveSessions(userId);
}
/**
* Add message to session
*/
async addMessage(sessionId, message) {
const session = await this.storage.getSession(sessionId);
if (!session) {
throw new Error(`Session '${sessionId}' not found`);
}
session.messages.push(message);
session.messageCount++;
session.lastActivity = new Date();
// Update session status to active if it was paused
if (session.status === 'paused') {
session.status = 'active';
}
await this.storage.updateSession(session);
// Emit event
this.events.messageAdded?.(sessionId, message);
}
/**
* Update session context
*/
async updateContext(sessionId, context) {
const session = await this.storage.getSession(sessionId);
if (!session) {
throw new Error(`Session '${sessionId}' not found`);
}
// Merge context updates
session.context = {
...session.context,
...context,
// Handle array merging properly
solutions: [...(session.context.solutions || []), ...(context.solutions || [])],
recommendations: [...(session.context.recommendations || []), ...(context.recommendations || [])],
nextSteps: [...(session.context.nextSteps || []), ...(context.nextSteps || [])],
userPreferences: {
...session.context.userPreferences,
...context.userPreferences
}
};
session.lastActivity = new Date();
await this.storage.updateSession(session);
}
/**
* Cleanup expired sessions
*/
async cleanupSessions() {
return await this.storage.cleanupExpiredSessions();
}
/**
* Subscribe to session events
*/
on(event, handler) {
this.events[event] = handler;
}
/**
* Get session statistics
*/
async getSessionStats(userId) {
const sessions = userId
? await this.storage.getUserSessions(userId)
: await this.getAllSessionSummaries();
const totalSessions = sessions.length;
const activeSessions = sessions.filter(s => s.status === 'active' || s.status === 'paused').length;
const totalMessages = sessions.reduce((sum, s) => sum + s.messageCount, 0);
const averageSessionLength = totalSessions > 0 ? totalMessages / totalSessions : 0;
return {
totalSessions,
activeSessions,
totalMessages,
averageSessionLength: Math.round(averageSessionLength * 100) / 100
};
}
/**
* Initialize storage based on configuration
*/
initializeStorage(config) {
console.error(`🗄️ Initializing session storage: ${config?.type || 'memory'}`);
switch (config?.type || 'memory') {
case 'memory':
return new InMemorySessionStorage(config);
case 'file':
return new FileSessionStorage(config);
case 'database':
// TODO: Implement DatabaseSessionStorage
throw new Error('Database storage not yet implemented. Use memory or file storage for now.');
case 'mcp':
// TODO: Implement MCPSessionStorage
throw new Error('MCP storage not yet implemented. Use memory or file storage for now.');
default:
console.warn(`Unknown storage type '${config?.type}', falling back to memory storage`);
return new InMemorySessionStorage(config);
}
}
/**
* Get all session summaries (for stats)
*/
async getAllSessionSummaries() {
// This is a bit of a hack since we don't have a "get all sessions" method
// In practice, this would be implemented differently for each storage backend
if (this.storage instanceof InMemorySessionStorage) {
// For in-memory storage, we can access the internal data
return []; // Simplified for now
}
return [];
}
}
//# sourceMappingURL=specialist-session-manager.js.map