mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 16:39: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;
|
name?: string;
|
||||||
screenManager?: ScreenManager;
|
screenManager?: ScreenManager;
|
||||||
useScreen?: boolean;
|
useScreen?: boolean;
|
||||||
|
screenSession?: ScreenSession; // For restored sessions - pass the existing screen
|
||||||
}) {
|
}) {
|
||||||
super();
|
super();
|
||||||
this.id = config.id || uuidv4();
|
this.id = config.id || uuidv4();
|
||||||
@@ -139,6 +140,7 @@ export class Session extends EventEmitter {
|
|||||||
this._lastActivityAt = this.createdAt;
|
this._lastActivityAt = this.createdAt;
|
||||||
this._screenManager = config.screenManager || null;
|
this._screenManager = config.screenManager || null;
|
||||||
this._useScreen = config.useScreen ?? (this._screenManager !== null && ScreenManager.isScreenAvailable());
|
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
|
// Initialize task tracker and forward events
|
||||||
this._taskTracker = new TaskTracker();
|
this._taskTracker = new TaskTracker();
|
||||||
@@ -379,14 +381,21 @@ export class Session extends EventEmitter {
|
|||||||
|
|
||||||
console.log('[Session] Starting interactive Claude session' + (this._useScreen ? ' (with screen)' : ''));
|
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) {
|
if (this._useScreen && this._screenManager) {
|
||||||
try {
|
try {
|
||||||
this._screenSession = await this._screenManager.createScreen(this.id, this.workingDir, 'claude', this._name);
|
// Check if we already have a screen session (restored session)
|
||||||
console.log('[Session] Created screen session:', this._screenSession.screenName);
|
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
|
// Wait a moment for screen to fully start
|
||||||
await new Promise(resolve => setTimeout(resolve, 300));
|
await new Promise(resolve => setTimeout(resolve, 300));
|
||||||
|
}
|
||||||
|
|
||||||
// Attach to the screen session via PTY
|
// Attach to the screen session via PTY
|
||||||
this.ptyProcess = pty.spawn('screen', [
|
this.ptyProcess = pty.spawn('screen', [
|
||||||
@@ -399,12 +408,14 @@ export class Session extends EventEmitter {
|
|||||||
env: { ...process.env, TERM: 'xterm-256color' },
|
env: { ...process.env, TERM: 'xterm-256color' },
|
||||||
});
|
});
|
||||||
|
|
||||||
// Screen creates blank space when initializing. After attaching, wait for
|
// For NEW screens: clear buffer after initial burst (screen initialization noise)
|
||||||
// the initial burst then clear the buffer and tell clients to clear their terminal.
|
// For RESTORED screens: don't clear - we want to see the existing output
|
||||||
setTimeout(() => {
|
if (!isRestoredSession) {
|
||||||
this._terminalBuffer = '';
|
setTimeout(() => {
|
||||||
this.emit('clearTerminal');
|
this._terminalBuffer = '';
|
||||||
}, 100);
|
this.emit('clearTerminal');
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[Session] Failed to create screen session, falling back to direct PTY:', err);
|
console.error('[Session] Failed to create screen session, falling back to direct PTY:', err);
|
||||||
this._useScreen = false;
|
this._useScreen = false;
|
||||||
@@ -510,14 +521,21 @@ export class Session extends EventEmitter {
|
|||||||
const shell = process.env.SHELL || '/bin/bash';
|
const shell = process.env.SHELL || '/bin/bash';
|
||||||
console.log('[Session] Starting shell session with:', shell + (this._useScreen ? ' (with screen)' : ''));
|
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) {
|
if (this._useScreen && this._screenManager) {
|
||||||
try {
|
try {
|
||||||
this._screenSession = await this._screenManager.createScreen(this.id, this.workingDir, 'shell', this._name);
|
// Check if we already have a screen session (restored session)
|
||||||
console.log('[Session] Created screen session:', this._screenSession.screenName);
|
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
|
// Wait a moment for screen to fully start
|
||||||
await new Promise(resolve => setTimeout(resolve, 300));
|
await new Promise(resolve => setTimeout(resolve, 300));
|
||||||
|
}
|
||||||
|
|
||||||
// Attach to the screen session via PTY
|
// Attach to the screen session via PTY
|
||||||
this.ptyProcess = pty.spawn('screen', [
|
this.ptyProcess = pty.spawn('screen', [
|
||||||
@@ -530,14 +548,16 @@ export class Session extends EventEmitter {
|
|||||||
env: { ...process.env, TERM: 'xterm-256color' },
|
env: { ...process.env, TERM: 'xterm-256color' },
|
||||||
});
|
});
|
||||||
|
|
||||||
// Screen creates blank space when initializing. After attaching, wait for
|
// For NEW screens: clear by sending 'clear' command to the shell
|
||||||
// the initial burst then clear by sending 'clear' command to the shell.
|
// For RESTORED screens: don't clear - we want to see the existing output
|
||||||
setTimeout(() => {
|
if (!isRestoredSession) {
|
||||||
if (this.ptyProcess) {
|
setTimeout(() => {
|
||||||
this._terminalBuffer = '';
|
if (this.ptyProcess) {
|
||||||
this.ptyProcess.write('clear\n');
|
this._terminalBuffer = '';
|
||||||
}
|
this.ptyProcess.write('clear\n');
|
||||||
}, 100);
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[Session] Failed to create screen session, falling back to direct PTY:', err);
|
console.error('[Session] Failed to create screen session, falling back to direct PTY:', err);
|
||||||
this._useScreen = false;
|
this._useScreen = false;
|
||||||
|
|||||||
@@ -633,6 +633,19 @@ class ClaudemanApp {
|
|||||||
this.activeSessionId = sessionId;
|
this.activeSessionId = sessionId;
|
||||||
this.renderSessionTabs();
|
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
|
// Load terminal buffer for this session
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/sessions/${sessionId}/terminal`);
|
const res = await fetch(`/api/sessions/${sessionId}/terminal`);
|
||||||
|
|||||||
+3
-2
@@ -1393,14 +1393,15 @@ export class WebServer extends EventEmitter {
|
|||||||
const screens = this.screenManager.getScreens();
|
const screens = this.screenManager.getScreens();
|
||||||
for (const screen of screens) {
|
for (const screen of screens) {
|
||||||
if (!this.sessions.has(screen.sessionId)) {
|
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({
|
const session = new Session({
|
||||||
id: screen.sessionId, // Preserve the original session ID
|
id: screen.sessionId, // Preserve the original session ID
|
||||||
workingDir: screen.workingDir,
|
workingDir: screen.workingDir,
|
||||||
mode: screen.mode,
|
mode: screen.mode,
|
||||||
name: screen.name || screen.screenName,
|
name: screen.name || screen.screenName,
|
||||||
screenManager: this.screenManager,
|
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);
|
this.sessions.set(session.id, session);
|
||||||
|
|||||||
Reference in New Issue
Block a user