diff --git a/src/session.ts b/src/session.ts index b0293c4d..2f7ba02a 100644 --- a/src/session.ts +++ b/src/session.ts @@ -129,6 +129,7 @@ export class Session extends EventEmitter { name?: string; screenManager?: ScreenManager; useScreen?: boolean; + screenSession?: ScreenSession; // For restored sessions - pass the existing screen }) { super(); this.id = config.id || uuidv4(); @@ -139,6 +140,7 @@ export class Session extends EventEmitter { this._lastActivityAt = this.createdAt; this._screenManager = config.screenManager || null; this._useScreen = config.useScreen ?? (this._screenManager !== null && ScreenManager.isScreenAvailable()); + this._screenSession = config.screenSession || null; // Use existing screen if provided // Initialize task tracker and forward events this._taskTracker = new TaskTracker(); @@ -379,14 +381,21 @@ export class Session extends EventEmitter { console.log('[Session] Starting interactive Claude session' + (this._useScreen ? ' (with screen)' : '')); - // If screen wrapping is enabled, create a screen session first + // If screen wrapping is enabled, create or attach to a screen session if (this._useScreen && this._screenManager) { try { - this._screenSession = await this._screenManager.createScreen(this.id, this.workingDir, 'claude', this._name); - console.log('[Session] Created screen session:', this._screenSession.screenName); + // Check if we already have a screen session (restored session) + const isRestoredSession = this._screenSession !== null; + if (isRestoredSession) { + console.log('[Session] Attaching to existing screen session:', this._screenSession.screenName); + } else { + // Create a new screen session + this._screenSession = await this._screenManager.createScreen(this.id, this.workingDir, 'claude', this._name); + console.log('[Session] Created screen session:', this._screenSession.screenName); - // Wait a moment for screen to fully start - await new Promise(resolve => setTimeout(resolve, 300)); + // Wait a moment for screen to fully start + await new Promise(resolve => setTimeout(resolve, 300)); + } // Attach to the screen session via PTY this.ptyProcess = pty.spawn('screen', [ @@ -399,12 +408,14 @@ export class Session extends EventEmitter { env: { ...process.env, TERM: 'xterm-256color' }, }); - // Screen creates blank space when initializing. After attaching, wait for - // the initial burst then clear the buffer and tell clients to clear their terminal. - setTimeout(() => { - this._terminalBuffer = ''; - this.emit('clearTerminal'); - }, 100); + // For NEW screens: clear buffer after initial burst (screen initialization noise) + // For RESTORED screens: don't clear - we want to see the existing output + if (!isRestoredSession) { + setTimeout(() => { + this._terminalBuffer = ''; + this.emit('clearTerminal'); + }, 100); + } } catch (err) { console.error('[Session] Failed to create screen session, falling back to direct PTY:', err); this._useScreen = false; @@ -510,14 +521,21 @@ export class Session extends EventEmitter { const shell = process.env.SHELL || '/bin/bash'; console.log('[Session] Starting shell session with:', shell + (this._useScreen ? ' (with screen)' : '')); - // If screen wrapping is enabled, create a screen session first + // If screen wrapping is enabled, create or attach to a screen session if (this._useScreen && this._screenManager) { try { - this._screenSession = await this._screenManager.createScreen(this.id, this.workingDir, 'shell', this._name); - console.log('[Session] Created screen session:', this._screenSession.screenName); + // Check if we already have a screen session (restored session) + const isRestoredSession = this._screenSession !== null; + if (isRestoredSession) { + console.log('[Session] Attaching to existing screen session:', this._screenSession.screenName); + } else { + // Create a new screen session + this._screenSession = await this._screenManager.createScreen(this.id, this.workingDir, 'shell', this._name); + console.log('[Session] Created screen session:', this._screenSession.screenName); - // Wait a moment for screen to fully start - await new Promise(resolve => setTimeout(resolve, 300)); + // Wait a moment for screen to fully start + await new Promise(resolve => setTimeout(resolve, 300)); + } // Attach to the screen session via PTY this.ptyProcess = pty.spawn('screen', [ @@ -530,14 +548,16 @@ export class Session extends EventEmitter { env: { ...process.env, TERM: 'xterm-256color' }, }); - // Screen creates blank space when initializing. After attaching, wait for - // the initial burst then clear by sending 'clear' command to the shell. - setTimeout(() => { - if (this.ptyProcess) { - this._terminalBuffer = ''; - this.ptyProcess.write('clear\n'); - } - }, 100); + // For NEW screens: clear by sending 'clear' command to the shell + // For RESTORED screens: don't clear - we want to see the existing output + if (!isRestoredSession) { + setTimeout(() => { + if (this.ptyProcess) { + this._terminalBuffer = ''; + this.ptyProcess.write('clear\n'); + } + }, 100); + } } catch (err) { console.error('[Session] Failed to create screen session, falling back to direct PTY:', err); this._useScreen = false; diff --git a/src/web/public/app.js b/src/web/public/app.js index 36a90465..fa6b0f8c 100644 --- a/src/web/public/app.js +++ b/src/web/public/app.js @@ -633,6 +633,19 @@ class ClaudemanApp { this.activeSessionId = sessionId; this.renderSessionTabs(); + // Check if this is a restored session that needs to be attached + const session = this.sessions.get(sessionId); + if (session && session.pid === null && session.status === 'idle') { + // This is a restored session - attach to the existing screen + try { + await fetch(`/api/sessions/${sessionId}/interactive`, { method: 'POST' }); + // Update local session state + session.status = 'busy'; + } catch (err) { + console.error('Failed to attach to restored session:', err); + } + } + // Load terminal buffer for this session try { const res = await fetch(`/api/sessions/${sessionId}/terminal`); diff --git a/src/web/server.ts b/src/web/server.ts index 84096c00..f4a4e161 100644 --- a/src/web/server.ts +++ b/src/web/server.ts @@ -1393,14 +1393,15 @@ export class WebServer extends EventEmitter { const screens = this.screenManager.getScreens(); for (const screen of screens) { if (!this.sessions.has(screen.sessionId)) { - // Create a session object for this screen + // Create a session object for this screen with the existing screenSession const session = new Session({ id: screen.sessionId, // Preserve the original session ID workingDir: screen.workingDir, mode: screen.mode, name: screen.name || screen.screenName, screenManager: this.screenManager, - useScreen: true + useScreen: true, + screenSession: screen // Pass the existing screen so startInteractive() can attach to it }); this.sessions.set(session.id, session);