diff --git a/web/src/fwd.ts b/web/src/fwd.ts index a580e72b..327afad7 100755 --- a/web/src/fwd.ts +++ b/web/src/fwd.ts @@ -82,6 +82,10 @@ async function main() { console.log(`Session created with ID: ${result.sessionId}`); console.log(`Implementation: ${ptyService.getCurrentImplementation()}`); + // Track all intervals and streams for cleanup + const intervals: NodeJS.Timeout[] = []; + const streams: any[] = []; + // Get session info const session = ptyService.getSession(result.sessionId); if (!session) { @@ -152,6 +156,7 @@ async function main() { // Unix FIFO approach const controlFd = fs.openSync(controlPath, 'r+'); const controlStream = fs.createReadStream('', { fd: controlFd, encoding: 'utf8' }); + streams.push(controlStream); controlStream.on('data', (chunk: string | Buffer) => { const data = chunk.toString('utf8'); @@ -224,18 +229,7 @@ async function main() { // Poll every 100ms on Windows const controlInterval = setInterval(pollControl, 100); - - // Clean up control polling on exit - process.on('exit', () => { - clearInterval(controlInterval); - try { - if (fs.existsSync(controlPath)) { - fs.unlinkSync(controlPath); - } - } catch (_e) { - // Ignore cleanup errors - } - }); + intervals.push(controlInterval); } // Handle control messages @@ -307,6 +301,7 @@ async function main() { // Open FIFO for both read and write (like tty-fwd) to keep it open const stdinFd = fs.openSync(stdinPath, 'r+'); // r+ = read/write const stdinStream = fs.createReadStream('', { fd: stdinFd, encoding: 'utf8' }); + streams.push(stdinStream); stdinStream.on('data', (chunk: string | Buffer) => { const data = chunk.toString('utf8'); @@ -382,9 +377,33 @@ async function main() { if (line.trim()) { try { const record = JSON.parse(line); - if (Array.isArray(record) && record.length >= 3 && record[1] === 'o') { - // This is an output record: [timestamp, 'o', text] - process.stdout.write(record[2]); + if (Array.isArray(record) && record.length >= 3) { + if (record[1] === 'o') { + // This is an output record: [timestamp, 'o', text] + process.stdout.write(record[2]); + } else if (record[0] === 'exit') { + // This is an exit event: ['exit', exitCode, sessionId] + console.log(`\n\nDetected exit event with code: ${record[1]}`); + // Clean up all intervals and streams immediately + intervals.forEach((interval) => clearInterval(interval)); + streams.forEach((stream) => { + try { + stream.destroy?.(); + } catch (_e) { + // Ignore cleanup errors + } + }); + + // Restore terminal settings + if (!monitorOnly && process.stdin.isTTY) { + process.stdin.setRawMode(false); + } + if (!monitorOnly) { + process.stdin.pause(); + } + + process.exit(record[1] || 0); + } } } catch (_e) { // If JSON parse fails, might be partial line, skip it @@ -401,11 +420,7 @@ async function main() { // Start monitoring const streamInterval = setInterval(readNewData, 50); - - // Clean up on exit - process.on('exit', () => { - clearInterval(streamInterval); - }); + intervals.push(streamInterval); } // Set up signal handlers for graceful shutdown @@ -444,11 +459,26 @@ async function main() { process.on('SIGINT', () => shutdown('SIGINT')); process.on('SIGTERM', () => shutdown('SIGTERM')); - // Monitor session status + // Monitor session status with faster polling and better exit detection const checkInterval = setInterval(() => { try { const currentSession = ptyService.getSession(result.sessionId); if (!currentSession || currentSession.status === 'exited') { + console.log('\n\nSession has exited.'); + if (currentSession?.exit_code !== undefined) { + console.log(`Exit code: ${currentSession.exit_code}`); + } + + // Clean up all intervals and streams + intervals.forEach((interval) => clearInterval(interval)); + streams.forEach((stream) => { + try { + stream.destroy?.(); + } catch (_e) { + // Ignore cleanup errors + } + }); + // Restore terminal settings before exit (only if we were in interactive mode) if (!monitorOnly && process.stdin.isTTY) { process.stdin.setRawMode(false); @@ -457,19 +487,24 @@ async function main() { process.stdin.pause(); } - console.log('\n\nSession has exited.'); - if (currentSession?.exit_code !== undefined) { - console.log(`Exit code: ${currentSession.exit_code}`); - } - clearInterval(checkInterval); process.exit(currentSession?.exit_code || 0); } } catch (error) { console.error('Error monitoring session:', error); - clearInterval(checkInterval); + // Clean up all intervals and streams on error too + intervals.forEach((interval) => clearInterval(interval)); + streams.forEach((stream) => { + try { + stream.destroy?.(); + } catch (_e) { + // Ignore cleanup errors + } + }); process.exit(1); } - }, 1000); // Check every second + }, 500); // Check every 500ms for faster exit detection + + intervals.push(checkInterval); // Keep the process alive await new Promise((resolve) => { diff --git a/web/src/pty/PtyManager.ts b/web/src/pty/PtyManager.ts index 5309129b..e7bd1ea6 100644 --- a/web/src/pty/PtyManager.ts +++ b/web/src/pty/PtyManager.ts @@ -308,43 +308,6 @@ export class PtyManager { } } - /** - * Try to create a control pipe for an existing external session - */ - private createControlPipeForExternalSession(sessionId: string): string | null { - const diskSession = this.sessionManager.getSession(sessionId); - if (!diskSession || !diskSession.stdin) { - return null; - } - - try { - // Create control pipe in the same directory as stdin - const controlPipePath = path.join(path.dirname(diskSession.stdin), 'control'); - - // Create the control pipe file if it doesn't exist - if (!fs.existsSync(controlPipePath)) { - fs.writeFileSync(controlPipePath, ''); - } - - // Update session.json to include the control pipe - const sessionDir = path.dirname(diskSession.stdin); - const sessionInfoPath = path.join(sessionDir, 'session.json'); - - if (fs.existsSync(sessionInfoPath)) { - const sessionInfo = JSON.parse(fs.readFileSync(sessionInfoPath, 'utf8')); - sessionInfo.control = controlPipePath; - fs.writeFileSync(sessionInfoPath, JSON.stringify(sessionInfo, null, 2)); - - console.log(`Created control pipe for external session ${sessionId}: ${controlPipePath}`); - return controlPipePath; - } - } catch (error) { - console.warn(`Failed to create control pipe for session ${sessionId}:`, error); - } - - return null; - } - /** * Send a control message to an external session */ @@ -357,15 +320,14 @@ export class PtyManager { return false; } - let controlPipe = diskSession.control; + const controlPipe = diskSession.control; - // If no control pipe exists, try to create one for external sessions + // External sessions should already have control pipe created by fwd.ts if (!controlPipe) { - const createdPipe = this.createControlPipeForExternalSession(sessionId); - if (!createdPipe) { - return false; - } - controlPipe = createdPipe; + console.warn( + `No control pipe found for session ${sessionId} - external session should create its own control pipe` + ); + return false; } try {