/** * Terminal Gateway Module * * Provides WebSocket-based real-time terminal streaming for PTY and in-process sessions. * Uses event-based streaming instead of polling for better performance. * * In-process sessions (Crewly Agent runtime) are streamed via InProcessLogBuffer * EventEmitter events, while PTY sessions use native onData events. * * @module terminal-gateway */ import { Server as SocketIOServer, Socket } from 'socket.io'; /** * Terminal Gateway class for WebSocket-based terminal streaming. * * Provides: * - Real-time terminal output streaming via PTY onData events * - Terminal input forwarding to PTY sessions * - Terminal resize handling * - Session subscription management */ export declare class TerminalGateway { private io; private logger; /** Map of session name to set of subscribed socket IDs */ private connectedClients; /** Map of session name to unsubscribe function for PTY onData */ private sessionSubscriptions; /** Set of sessions with persistent monitoring (not stopped on client disconnect) */ private persistentMonitoringSessions; /** * Pending retry timers for eager monitoring attempts that fired before the * PTY session was registered in the SessionBackend. * * Keyed by session name. Cleared on first successful attach, on stop, and * on destroy so a backoff schedule never leaks a timer past the gateway. */ private pendingMonitoringRetries; /** Current active chat conversation ID for orchestrator responses */ private activeConversationId; /** * Sessions in read-only mode for security auditing. * Terminal output is streamed normally, but all input is blocked. * Used for compliance auditing and observing agent behavior safely. */ private readOnlySessions; /** * Set of in-process sessions currently being streamed via InProcessLogBuffer. * These sessions are automatically read-only (no PTY to write to). */ private inProcessStreamingSessions; /** * Bound handler for InProcessLogBuffer 'data' events. * Stored so it can be removed on destroy. */ private inProcessDataHandler; /** * Create a new TerminalGateway. * * @param io - Socket.IO server instance */ constructor(io: SocketIOServer); /** * Set up WebSocket event handlers. */ private setupEventHandlers; /** * Set up real-time log streaming from InProcessLogBuffer. * * Listens for 'data' events emitted when InProcessLogBuffer.append() is called, * and broadcasts the formatted log line to all WebSocket subscribers of that session. */ private setupInProcessLogStreaming; /** * Check if a session is an in-process Crewly Agent session. * * @param sessionName - The session to check * @returns True if the session exists in InProcessLogBuffer */ isInProcessSession(sessionName: string): boolean; /** * Subscribe a client to a specific terminal session. * * @param sessionName - The session to subscribe to * @param socket - The client socket */ subscribeToSession(sessionName: string, socket: Socket): void; /** * Start streaming logs from an in-process Crewly Agent session. * * In-process sessions are automatically read-only (no PTY to write to). * Real-time streaming is handled by the InProcessLogBuffer 'data' event * listener set up in setupInProcessLogStreaming(). * * @param sessionName - The session to stream from * @returns True if the session is an in-process session and streaming started */ private startInProcessStreaming; /** * Start streaming output from a PTY session. * Uses event-based onData instead of polling. * * @param sessionName - The session to stream from * @returns True if streaming started successfully, false otherwise */ private startPtyStreaming; /** * Stop streaming output from a PTY session. * Does not stop sessions with persistent monitoring enabled. * * @param sessionName - The session to stop streaming from * @param force - Force stop even for persistent monitoring sessions */ private stopPtyStreaming; /** * Start persistent monitoring for the orchestrator session. * * Ensures chat responses are captured even when no WebSocket clients are * viewing the terminal. If the PTY session is not yet registered in the * SessionBackend (e.g. eager call from boot fires before * createAgentSession's backend registration completes), schedules bounded * retries on the {@link TERMINAL_GATEWAY_CONSTANTS.MONITORING_RETRY_BACKOFFS_MS} * backoff schedule. Persistent flag stays set across retries so a parallel * subscriber path doesn't bypass eventual attachment. * * @param sessionName - The orchestrator session name to monitor * @returns True if monitoring started synchronously on first attempt; false * if a retry was scheduled (caller-visible failure is reserved for * terminal failure after all retries, surfaced via ERROR log only). */ /** * Whether this session is served by the in-process Crewly Agent runtime, * which has no PTY. * * Returns false when nothing has been wired yet: that keeps the old retry * behaviour rather than silently skipping a session that does have a PTY. * * @param sessionName - The session to check * @returns True when the session runs in-process */ private inProcessRuntimeCheck?; private isInProcessOrchestrator; /** * Tell the gateway how to recognise a session with no PTY. * * Wired from boot with `AgentRegistrationService.isInProcessRuntimeActive`; * injected rather than imported because that service is owned by the API * controller, and because reaching it through a bare `require` inside an * ESM build fails silently — which would turn this guard into a no-op. * * @param check - Predicate answering whether a session runs in-process */ setInProcessRuntimeCheck(check: (sessionName: string) => boolean): void; startOrchestratorChatMonitoring(sessionName: string): boolean; /** * Schedule the next bounded retry attempt to attach orchestrator monitoring. * * @param sessionName - Session name being monitored * @param attemptIndex - 0-based index into MONITORING_RETRY_BACKOFFS_MS */ private scheduleMonitoringRetry; /** * Clear any pending monitoring retry timer for a session. * * Idempotent — safe to call when no retry is queued. * * @param sessionName - Session name whose retry should be cleared */ private clearPendingMonitoringRetry; /** * Stop persistent monitoring for the orchestrator session. * * Cancels any in-flight retry schedule so a stop mid-backoff doesn't leak * a timer that later attaches to a session the caller already gave up on. * * @param sessionName - The orchestrator session name */ stopOrchestratorChatMonitoring(sessionName: string): void; /** * Clean up session subscription when session ends. * * @param sessionName - The session to clean up */ private cleanupSessionSubscription; /** * Unsubscribe a client from a terminal session. * * @param sessionName - The session to unsubscribe from * @param socket - The client socket */ unsubscribeFromSession(sessionName: string, socket: Socket): void; /** * Send input to a terminal session. * * @param sessionName - The session to send input to * @param input - The input string * @param socket - The client socket */ sendInput(sessionName: string, input: string, socket: Socket): Promise; /** * Handle terminal resize events. * * @param sessionName - The session to resize * @param cols - New column count * @param rows - New row count */ private handleTerminalResize; /** * Handle client disconnection. * * @param socket - The disconnected client socket */ private handleClientDisconnect; /** * Broadcast terminal output to all subscribers of a session. * Also processes orchestrator output for chat responses. * * @param sessionName - The session name * @param output - The terminal output */ private broadcastOutput; /** * Enable read-only mode for a session. Output streaming continues but * all input is blocked. Used for security auditing and safe observation. * * @param sessionName - The session to set as read-only */ setReadOnly(sessionName: string): void; /** * Disable read-only mode for a session, restoring normal input capability. * * @param sessionName - The session to restore */ clearReadOnly(sessionName: string): void; /** * Check if a session is in read-only (audit) mode. * * @param sessionName - The session to check * @returns True if the session is read-only */ isReadOnly(sessionName: string): boolean; /** * Set the active conversation ID for orchestrator chat responses. * * @param conversationId - The conversation ID to set */ setActiveConversationId(conversationId: string | null): void; /** * Get the active conversation ID. * * @returns The active conversation ID or null */ getActiveConversationId(): string | null; /** * Broadcast session status changes. * * @param sessionName - The session name * @param status - The status string */ private broadcastSessionStatus; /** * Broadcast general messages to session subscribers. * * @param sessionName - The session name * @param type - Message type * @param payload - Message payload */ private broadcastMessage; /** * Send current terminal state to a new subscriber. * * @param sessionName - The session name * @param socket - The client socket */ private sendCurrentTerminalState; /** * Get statistics about connected clients. * * @returns Connection statistics */ getConnectionStats(): { totalClients: number; sessionSubscriptions: Record; totalSessions: number; activeStreams: number; }; /** * Force disconnect all clients from a session. * * @param sessionName - The session name */ disconnectSessionClients(sessionName: string): void; /** * Broadcast system-wide notifications. * * @param message - Notification message * @param type - Notification type */ broadcastSystemNotification(message: string, type?: 'info' | 'warning' | 'error'): void; /** * Broadcast orchestrator status changes. * * @param orchestratorData - Orchestrator data */ broadcastOrchestratorStatus(orchestratorData: unknown): void; /** * Broadcast team member status changes. * * @param memberData - Team member data */ broadcastTeamMemberStatus(memberData: unknown): void; /** * Broadcast a system resource alert to all connected clients. * * @param alertData - Alert details including key, message, severity, and timestamp */ broadcastSystemResourceAlert(alertData: { alertKey: string; message: string; severity: string; timestamp: string; }): void; /** * Broadcast context window status updates to all connected clients. * * @param contextData - Context window status data */ broadcastContextWindowStatus(contextData: { sessionName: string; memberId?: string; teamId?: string; contextPercent: number; level: string; timestamp: string; }): void; /** * Broadcast comprehensive team activity updates. * * @param activityData - Activity data */ broadcastTeamActivity(activityData: unknown): void; /** * Destroy the gateway and clean up all subscriptions. */ destroy(): void; } /** * Set the terminal gateway singleton instance. * Called during server initialization. * * @param gateway - The TerminalGateway instance */ export declare function setTerminalGateway(gateway: TerminalGateway): void; /** * Get the terminal gateway singleton instance. * * @returns The TerminalGateway instance or null if not initialized */ export declare function getTerminalGateway(): TerminalGateway | null; /** * Reset the terminal gateway singleton instance (for testing). */ export declare function resetTerminalGateway(): void; //# sourceMappingURL=terminal.gateway.d.ts.map