mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-02 05:29:42 +02:00
fix: session restoration now properly attaches to existing screens
- Pass existing screenSession to Session constructor for restored sessions - Skip terminal buffer clearing when attaching to restored (existing) screens - Frontend auto-calls /interactive endpoint when selecting a restored session - Fixes issue where restored sessions appeared broken after server restart Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
+44
-24
@@ -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;
|
||||
|
||||
@@ -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`);
|
||||
|
||||
+3
-2
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user