diff --git a/src/session-manager.ts b/src/session-manager.ts index 9e528729..48ef0cfd 100644 --- a/src/session-manager.ts +++ b/src/session-manager.ts @@ -1,20 +1,60 @@ +/** + * @fileoverview Session Manager for coordinating multiple Claude sessions + * + * Provides lifecycle management for Claude CLI sessions: + * - Session creation with working directory configuration + * - Event forwarding from individual sessions + * - State persistence via StateStore + * - Concurrent session limits + * + * @module session-manager + */ + import { EventEmitter } from 'node:events'; import { Session } from './session.js'; import { getStore } from './state-store.js'; import { SessionState } from './types.js'; +/** + * Events emitted by SessionManager + */ export interface SessionManagerEvents { + /** Fired when a new session starts successfully */ sessionStarted: (session: Session) => void; + /** Fired when a session stops (graceful or forced) */ sessionStopped: (sessionId: string) => void; + /** Fired when a session encounters an error */ sessionError: (sessionId: string, error: string) => void; + /** Fired when a session produces terminal output */ sessionOutput: (sessionId: string, output: string) => void; + /** Fired when a completion phrase is detected */ sessionCompletion: (sessionId: string, phrase: string) => void; } +/** + * Manages multiple Claude sessions with lifecycle coordination. + * + * @description + * SessionManager acts as a coordinator for multiple Claude CLI sessions: + * - Enforces concurrent session limits from config + * - Forwards session events to subscribers + * - Persists session state to disk + * - Handles graceful shutdown + * + * @extends EventEmitter + * @fires SessionManagerEvents.sessionStarted + * @fires SessionManagerEvents.sessionStopped + * @fires SessionManagerEvents.sessionError + * @fires SessionManagerEvents.sessionOutput + * @fires SessionManagerEvents.sessionCompletion + */ export class SessionManager extends EventEmitter { private sessions: Map = new Map(); private store = getStore(); + /** + * Creates a new SessionManager and loads previous session state. + */ constructor() { super(); this.loadFromStore(); @@ -33,6 +73,13 @@ export class SessionManager extends EventEmitter { } } + /** + * Creates and starts a new Claude session. + * + * @param workingDir - Working directory for the session + * @returns The newly created session + * @throws Error if max concurrent sessions limit reached + */ async createSession(workingDir: string): Promise { const config = this.store.getConfig(); @@ -71,6 +118,11 @@ export class SessionManager extends EventEmitter { return session; } + /** + * Stops a session by ID. + * + * @param id - Session ID to stop + */ async stopSession(id: string): Promise { const session = this.sessions.get(id); if (!session) { @@ -89,6 +141,9 @@ export class SessionManager extends EventEmitter { this.updateSessionState(session); } + /** + * Stops all active sessions. + */ async stopAllSessions(): Promise { const stopPromises = Array.from(this.sessions.keys()).map((id) => this.stopSession(id) @@ -96,26 +151,36 @@ export class SessionManager extends EventEmitter { await Promise.all(stopPromises); } + /** + * Gets a session by ID. + * @param id - Session ID + * @returns The session or undefined if not found + */ getSession(id: string): Session | undefined { return this.sessions.get(id); } + /** Gets all active sessions. */ getAllSessions(): Session[] { return Array.from(this.sessions.values()); } + /** Gets all sessions currently idle (not processing). */ getIdleSessions(): Session[] { return this.getAllSessions().filter((s) => s.isIdle()); } + /** Gets all sessions currently busy (processing). */ getBusySessions(): Session[] { return this.getAllSessions().filter((s) => s.isBusy()); } + /** Gets the count of active sessions. */ getSessionCount(): number { return this.sessions.size; } + /** Checks if a session exists by ID. */ hasSession(id: string): boolean { return this.sessions.has(id); } @@ -124,10 +189,18 @@ export class SessionManager extends EventEmitter { this.store.setSession(session.id, session.toState()); } + /** Gets all sessions from persistent storage (including stopped). */ getStoredSessions(): Record { return this.store.getSessions(); } + /** + * Sends input to a session. + * + * @param sessionId - Session ID to send to + * @param input - Input string to send + * @throws Error if session not found + */ async sendToSession(sessionId: string, input: string): Promise { const session = this.sessions.get(sessionId); if (!session) { @@ -136,11 +209,13 @@ export class SessionManager extends EventEmitter { await session.sendInput(input); } + /** Gets the output buffer for a session. */ getSessionOutput(sessionId: string): string | null { const session = this.sessions.get(sessionId); return session?.getOutput() ?? null; } + /** Gets the error buffer for a session. */ getSessionError(sessionId: string): string | null { const session = this.sessions.get(sessionId); return session?.getError() ?? null; @@ -150,6 +225,10 @@ export class SessionManager extends EventEmitter { // Singleton instance let managerInstance: SessionManager | null = null; +/** + * Gets or creates the singleton SessionManager instance. + * @returns The global SessionManager + */ export function getSessionManager(): SessionManager { if (!managerInstance) { managerInstance = new SessionManager();