@@ -48,6 +48,78 @@ function getNextSessionNumber(): number {
4848 return Math . max ( ...sessions . map ( s => s . number ) ) + 1 ;
4949}
5050
51+ // Helper function to spawn PTY and setup coding agent
52+ function spawnSessionPty (
53+ sessionId : string ,
54+ worktreePath : string ,
55+ config : SessionConfig ,
56+ sessionUuid : string ,
57+ isNewSession : boolean
58+ ) {
59+ const shell = os . platform ( ) === "darwin" ? "zsh" : "bash" ;
60+ const ptyProcess = pty . spawn ( shell , [ "-l" ] , {
61+ name : "xterm-color" ,
62+ cols : 80 ,
63+ rows : 30 ,
64+ cwd : worktreePath ,
65+ env : process . env ,
66+ } ) ;
67+
68+ activePtyProcesses . set ( sessionId , ptyProcess ) ;
69+
70+ let terminalReady = false ;
71+ let dataBuffer = "" ;
72+
73+ ptyProcess . onData ( ( data ) => {
74+ // Only send data if window still exists and is not destroyed
75+ if ( mainWindow && ! mainWindow . isDestroyed ( ) ) {
76+ mainWindow . webContents . send ( "session-output" , sessionId , data ) ;
77+ }
78+
79+ // Detect when terminal is ready
80+ if ( ! terminalReady ) {
81+ dataBuffer += data ;
82+
83+ // Method 1: Look for bracketed paste mode enable sequence
84+ // Method 2: Fallback - look for common prompt indicators
85+ const isReady = dataBuffer . includes ( "\x1b[?2004h" ) ||
86+ dataBuffer . includes ( "$ " ) || dataBuffer . includes ( "% " ) ||
87+ dataBuffer . includes ( "> " ) || dataBuffer . includes ( "➜ " ) ||
88+ dataBuffer . includes ( "➜ " ) || dataBuffer . includes ( "✗ " ) ||
89+ dataBuffer . includes ( "✓ " ) || dataBuffer . endsWith ( "$" ) ||
90+ dataBuffer . endsWith ( "%" ) || dataBuffer . endsWith ( ">" ) ||
91+ dataBuffer . endsWith ( "➜" ) || dataBuffer . endsWith ( "✗" ) ||
92+ dataBuffer . endsWith ( "✓" ) ;
93+
94+ if ( isReady ) {
95+ terminalReady = true ;
96+
97+ // Run setup commands if provided
98+ if ( config . setupCommands && config . setupCommands . length > 0 ) {
99+ config . setupCommands . forEach ( cmd => {
100+ ptyProcess . write ( cmd + "\r" ) ;
101+ } ) ;
102+ }
103+
104+ // Auto-run the selected coding agent
105+ if ( config . codingAgent === "claude" ) {
106+ const sessionFlag = isNewSession
107+ ? `--session-id ${ sessionUuid } `
108+ : `--resume ${ sessionUuid } ` ;
109+ const skipPermissionsFlag = config . skipPermissions ? "--dangerously-skip-permissions" : "" ;
110+ const flags = [ sessionFlag , skipPermissionsFlag ] . filter ( f => f ) . join ( " " ) ;
111+ const claudeCmd = `claude ${ flags } \r` ;
112+ ptyProcess . write ( claudeCmd ) ;
113+ } else if ( config . codingAgent === "codex" ) {
114+ ptyProcess . write ( "codex\r" ) ;
115+ }
116+ }
117+ }
118+ } ) ;
119+
120+ return ptyProcess ;
121+ }
122+
51123// Git worktree helper functions
52124async function ensureFleetcodeExcluded ( projectDir : string ) {
53125 // Check if we've already initialized this project (persisted across app restarts)
@@ -204,85 +276,7 @@ ipcMain.on("create-session", async (event, config: SessionConfig) => {
204276 savePersistedSessions ( sessions ) ;
205277
206278 // Spawn PTY in worktree directory
207- const shell = os . platform ( ) === "darwin" ? "zsh" : "bash" ;
208- const ptyProcess = pty . spawn ( shell , [ ] , {
209- name : "xterm-color" ,
210- cols : 80 ,
211- rows : 30 ,
212- cwd : worktreePath ,
213- env : process . env ,
214- } ) ;
215-
216- activePtyProcesses . set ( sessionId , ptyProcess ) ;
217-
218- let terminalReady = false ;
219- let dataBuffer = "" ;
220-
221- ptyProcess . onData ( ( data ) => {
222- // Only send data if window still exists and is not destroyed
223- if ( mainWindow && ! mainWindow . isDestroyed ( ) ) {
224- mainWindow . webContents . send ( "session-output" , sessionId , data ) ;
225- }
226-
227- // Detect when terminal is ready
228- if ( ! terminalReady ) {
229- dataBuffer += data ;
230-
231- // Method 1: Look for bracketed paste mode enable sequence
232- if ( dataBuffer . includes ( "\x1b[?2004h" ) ) {
233- terminalReady = true ;
234-
235- // Run setup commands if provided
236- if ( config . setupCommands && config . setupCommands . length > 0 ) {
237- config . setupCommands . forEach ( cmd => {
238- ptyProcess . write ( cmd + "\r" ) ;
239- } ) ;
240- }
241-
242- // Auto-run the selected coding agent
243- if ( config . codingAgent === "claude" ) {
244- // New session always uses --session-id
245- const sessionFlag = `--session-id ${ sessionUuid } ` ;
246- const skipPermissionsFlag = config . skipPermissions ? "--dangerously-skip-permissions" : "" ;
247- const flags = [ sessionFlag , skipPermissionsFlag ] . filter ( f => f ) . join ( " " ) ;
248- const claudeCmd = `claude ${ flags } \r` ;
249- ptyProcess . write ( claudeCmd ) ;
250- } else if ( config . codingAgent === "codex" ) {
251- ptyProcess . write ( "codex\r" ) ;
252- }
253- }
254-
255- // Method 2: Fallback - look for common prompt indicators
256- else if ( dataBuffer . includes ( "$ " ) || dataBuffer . includes ( "% " ) ||
257- dataBuffer . includes ( "> " ) || dataBuffer . includes ( "➜ " ) ||
258- dataBuffer . includes ( "➜ " ) ||
259- dataBuffer . includes ( "✗ " ) || dataBuffer . includes ( "✓ " ) ||
260- dataBuffer . endsWith ( "$" ) || dataBuffer . endsWith ( "%" ) ||
261- dataBuffer . endsWith ( ">" ) || dataBuffer . endsWith ( "➜" ) ||
262- dataBuffer . endsWith ( "✗" ) || dataBuffer . endsWith ( "✓" ) ) {
263- terminalReady = true ;
264-
265- // Run setup commands if provided
266- if ( config . setupCommands && config . setupCommands . length > 0 ) {
267- config . setupCommands . forEach ( cmd => {
268- ptyProcess . write ( cmd + "\r" ) ;
269- } ) ;
270- }
271-
272- // Auto-run the selected coding agent
273- if ( config . codingAgent === "claude" ) {
274- // New session always uses --session-id
275- const sessionFlag = `--session-id ${ sessionUuid } ` ;
276- const skipPermissionsFlag = config . skipPermissions ? "--dangerously-skip-permissions" : "" ;
277- const flags = [ sessionFlag , skipPermissionsFlag ] . filter ( f => f ) . join ( " " ) ;
278- const claudeCmd = `claude ${ flags } \r` ;
279- ptyProcess . write ( claudeCmd ) ;
280- } else if ( config . codingAgent === "codex" ) {
281- ptyProcess . write ( "codex\r" ) ;
282- }
283- }
284- }
285- } ) ;
279+ spawnSessionPty ( sessionId , worktreePath , config , sessionUuid , true ) ;
286280
287281 event . reply ( "session-created" , sessionId , persistedSession ) ;
288282 } catch ( error ) {
@@ -326,56 +320,7 @@ ipcMain.on("reopen-session", (event, sessionId: string) => {
326320 }
327321
328322 // Spawn new PTY in worktree directory
329- const shell = os . platform ( ) === "darwin" ? "zsh" : "bash" ;
330- const ptyProcess = pty . spawn ( shell , [ ] , {
331- name : "xterm-color" ,
332- cols : 80 ,
333- rows : 30 ,
334- cwd : session . worktreePath ,
335- env : process . env ,
336- } ) ;
337-
338- activePtyProcesses . set ( sessionId , ptyProcess ) ;
339-
340- let terminalReady = false ;
341- let dataBuffer = "" ;
342-
343- ptyProcess . onData ( ( data ) => {
344- // Only send data if window still exists and is not destroyed
345- if ( mainWindow && ! mainWindow . isDestroyed ( ) ) {
346- mainWindow . webContents . send ( "session-output" , sessionId , data ) ;
347- }
348-
349- // Detect when terminal is ready and auto-run agent
350- if ( ! terminalReady ) {
351- dataBuffer += data ;
352-
353- if ( dataBuffer . includes ( "\x1b[?2004h" ) ||
354- dataBuffer . includes ( "$ " ) || dataBuffer . includes ( "% " ) ||
355- dataBuffer . includes ( "➜ " ) || dataBuffer . includes ( "✗ " ) ||
356- dataBuffer . includes ( "✓ " ) ) {
357- terminalReady = true ;
358-
359- // Run setup commands if provided
360- if ( session . config . setupCommands && session . config . setupCommands . length > 0 ) {
361- session . config . setupCommands . forEach ( cmd => {
362- ptyProcess . write ( cmd + "\r" ) ;
363- } ) ;
364- }
365-
366- if ( session . config . codingAgent === "claude" ) {
367- // Reopened session always uses --resume
368- const sessionFlag = `--resume ${ session . sessionUuid } ` ;
369- const skipPermissionsFlag = session . config . skipPermissions ? "--dangerously-skip-permissions" : "" ;
370- const flags = [ sessionFlag , skipPermissionsFlag ] . filter ( f => f ) . join ( " " ) ;
371- const claudeCmd = `claude ${ flags } \r` ;
372- ptyProcess . write ( claudeCmd ) ;
373- } else if ( session . config . codingAgent === "codex" ) {
374- ptyProcess . write ( "codex\r" ) ;
375- }
376- }
377- }
378- } ) ;
323+ spawnSessionPty ( sessionId , session . worktreePath , session . config , session . sessionUuid , false ) ;
379324
380325 event . reply ( "session-reopened" , sessionId ) ;
381326} ) ;
0 commit comments