(id)
| 96 | const OUTPUT_BUFFER_MAX = 500 * 1024; |
| 97 | |
| 98 | function createSession(id) { |
| 99 | const shell = os.platform() === 'win32' ? 'powershell.exe' : 'bash'; |
| 100 | const term = pty.spawn(shell, [], { |
| 101 | name: 'xterm-256color', |
| 102 | cols: 80, |
| 103 | rows: 24, |
| 104 | cwd: process.env.CWD || os.homedir(), |
| 105 | env: (() => { const e = { ...process.env, TERM: 'xterm-256color' }; delete e.CLAUDECODE; delete e.CLAUDE_CODE; return e; })(), |
| 106 | }); |
| 107 | |
| 108 | const session = { |
| 109 | id, |
| 110 | term, |
| 111 | outputBuffer: '', |
| 112 | bufferSeq: 0, |
| 113 | sockets: new Set(), // All clients watching this session |
| 114 | exited: false, |
| 115 | }; |
| 116 | |
| 117 | console.log(`Terminal ${id} spawned (PID: ${term.pid})`); |
| 118 | |
| 119 | term.onData((data) => { |
| 120 | session.outputBuffer += data; |
| 121 | session.bufferSeq += data.length; |
| 122 | if (session.outputBuffer.length > OUTPUT_BUFFER_MAX) { |
| 123 | session.outputBuffer = session.outputBuffer.slice(-OUTPUT_BUFFER_MAX); |
| 124 | } |
| 125 | // Broadcast to all clients watching this session |
| 126 | for (const s of session.sockets) { |
| 127 | s.emit('output', data, session.bufferSeq, id); |
| 128 | } |
| 129 | }); |
| 130 | |
| 131 | term.onExit(({ exitCode }) => { |
| 132 | console.log(`Terminal ${id} exited (code: ${exitCode})`); |
| 133 | session.exited = true; |
| 134 | for (const s of session.sockets) { |
| 135 | s.emit('exit', exitCode, id); |
| 136 | } |
| 137 | sessions.delete(id); |
| 138 | // Notify all clients of updated session list |
| 139 | io.emit('sessions', getSessionList()); |
| 140 | }); |
| 141 | |
| 142 | sessions.set(id, session); |
| 143 | return session; |
| 144 | } |
| 145 | |
| 146 | function getSessionList() { |
| 147 | return Array.from(sessions.values()).map(s => ({ |
no test coverage detected