@@ -12,12 +12,27 @@ import {
1212 ipcMessageSchema ,
1313} from "@roo-code/types"
1414
15+ // Configuration for headless environments
16+ const HEADLESS_CONFIG = {
17+ // Increase retry attempts for headless environments
18+ maxRetries : 10 ,
19+ // Increase retry delay for slower environments
20+ retryDelay : 1000 ,
21+ // Connection timeout for headless environments (ms)
22+ connectionTimeout : 30000 ,
23+ // Enable verbose logging in headless mode
24+ verboseLogging : process . env . DISPLAY === ":99" || process . env . XVFB_DISPLAY !== undefined ,
25+ }
26+
1527export class IpcClient extends EventEmitter < IpcClientEvents > {
1628 private readonly _socketPath : string
1729 private readonly _id : string
1830 private readonly _log : ( ...args : unknown [ ] ) => void
1931 private _isConnected = false
2032 private _clientId ?: string
33+ private _connectionTimeout ?: NodeJS . Timeout
34+ private _shutdownInProgress = false
35+ private _reconnectAttempts = 0
2136
2237 constructor ( socketPath : string , log = console . log ) {
2338 super ( )
@@ -26,91 +41,277 @@ export class IpcClient extends EventEmitter<IpcClientEvents> {
2641 this . _id = `roo-code-evals-${ crypto . randomBytes ( 6 ) . toString ( "hex" ) } `
2742 this . _log = log
2843
44+ // Configure IPC for headless environments
2945 ipc . config . silent = true
46+ ipc . config . retry = HEADLESS_CONFIG . retryDelay
47+ ipc . config . maxRetries = HEADLESS_CONFIG . maxRetries
48+ ipc . config . stopRetrying = false
49+
50+ this . setupConnection ( )
51+ this . setupShutdownHandlers ( )
52+ }
53+
54+ private setupConnection ( ) {
55+ try {
56+ ipc . connectTo ( this . _id , this . socketPath , ( ) => {
57+ ipc . of [ this . _id ] ?. on ( "connect" , ( ) => this . onConnect ( ) )
58+ ipc . of [ this . _id ] ?. on ( "disconnect" , ( ) => this . onDisconnect ( ) )
59+ ipc . of [ this . _id ] ?. on ( "message" , ( data ) => this . onMessage ( data ) )
60+ ipc . of [ this . _id ] ?. on ( "error" , ( error ) => this . onError ( error ) )
61+ } )
62+
63+ // Set connection timeout for headless environments
64+ if ( HEADLESS_CONFIG . verboseLogging ) {
65+ this . _connectionTimeout = setTimeout ( ( ) => {
66+ if ( ! this . _isConnected && ! this . _shutdownInProgress ) {
67+ this . log (
68+ `[client#setupConnection] Connection timeout after ${ HEADLESS_CONFIG . connectionTimeout } ms` ,
69+ )
70+ this . handleConnectionFailure ( )
71+ }
72+ } , HEADLESS_CONFIG . connectionTimeout )
73+ }
74+ } catch ( error ) {
75+ this . log ( `[client#setupConnection] Error setting up connection: ${ error } ` )
76+ this . handleConnectionFailure ( )
77+ }
78+ }
79+
80+ private setupShutdownHandlers ( ) {
81+ const gracefulShutdown = async ( signal : string ) => {
82+ if ( this . _shutdownInProgress ) {
83+ return
84+ }
85+
86+ this . _shutdownInProgress = true
87+ this . log ( `[IpcClient] Received ${ signal } , initiating graceful shutdown...` )
88+
89+ try {
90+ await this . shutdown ( )
91+ } catch ( error ) {
92+ this . log ( `[IpcClient] Error during shutdown: ${ error } ` )
93+ }
94+ }
95+
96+ // Handle various termination signals
97+ process . once ( "SIGTERM" , ( ) => gracefulShutdown ( "SIGTERM" ) )
98+ process . once ( "SIGINT" , ( ) => gracefulShutdown ( "SIGINT" ) )
99+ process . once ( "SIGHUP" , ( ) => gracefulShutdown ( "SIGHUP" ) )
100+ }
101+
102+ private handleConnectionFailure ( ) {
103+ if ( this . _shutdownInProgress ) {
104+ return
105+ }
106+
107+ this . _reconnectAttempts ++
108+
109+ if ( this . _reconnectAttempts >= HEADLESS_CONFIG . maxRetries ) {
110+ this . log (
111+ `[client#handleConnectionFailure] Max reconnection attempts (${ HEADLESS_CONFIG . maxRetries } ) reached` ,
112+ )
113+ this . emit ( IpcMessageType . Disconnect )
114+ return
115+ }
116+
117+ this . log (
118+ `[client#handleConnectionFailure] Attempting reconnection ${ this . _reconnectAttempts } /${ HEADLESS_CONFIG . maxRetries } ` ,
119+ )
120+
121+ // Clear existing connection
122+ if ( ipc . of [ this . _id ] ) {
123+ ipc . disconnect ( this . _id )
124+ }
30125
31- ipc . connectTo ( this . _id , this . socketPath , ( ) => {
32- ipc . of [ this . _id ] ?. on ( "connect" , ( ) => this . onConnect ( ) )
33- ipc . of [ this . _id ] ?. on ( "disconnect" , ( ) => this . onDisconnect ( ) )
34- ipc . of [ this . _id ] ?. on ( "message" , ( data ) => this . onMessage ( data ) )
35- } )
126+ // Wait before reconnecting
127+ setTimeout ( ( ) => {
128+ if ( ! this . _shutdownInProgress ) {
129+ this . setupConnection ( )
130+ }
131+ } , HEADLESS_CONFIG . retryDelay * this . _reconnectAttempts )
132+ }
133+
134+ private onError ( error : unknown ) {
135+ this . log ( `[client#onError] IPC client error: ${ error } ` )
136+
137+ // In headless environments, try to recover from errors
138+ if ( HEADLESS_CONFIG . verboseLogging && ! this . _shutdownInProgress ) {
139+ this . log ( "[client#onError] Attempting to recover from error in headless environment..." )
140+ this . handleConnectionFailure ( )
141+ }
36142 }
37143
38144 private onConnect ( ) {
39- if ( this . _isConnected ) {
145+ if ( this . _isConnected || this . _shutdownInProgress ) {
40146 return
41147 }
42148
149+ // Clear connection timeout
150+ if ( this . _connectionTimeout ) {
151+ clearTimeout ( this . _connectionTimeout )
152+ this . _connectionTimeout = undefined
153+ }
154+
43155 this . log ( "[client#onConnect]" )
44156 this . _isConnected = true
157+ this . _reconnectAttempts = 0 // Reset reconnection attempts on successful connection
45158 this . emit ( IpcMessageType . Connect )
46159 }
47160
48161 private onDisconnect ( ) {
49- if ( ! this . _isConnected ) {
162+ if ( ! this . _isConnected || this . _shutdownInProgress ) {
50163 return
51164 }
52165
53166 this . log ( "[client#onDisconnect]" )
54167 this . _isConnected = false
168+ this . _clientId = undefined
169+
170+ // Clear connection timeout
171+ if ( this . _connectionTimeout ) {
172+ clearTimeout ( this . _connectionTimeout )
173+ this . _connectionTimeout = undefined
174+ }
175+
55176 this . emit ( IpcMessageType . Disconnect )
177+
178+ // Attempt reconnection in headless environments
179+ if ( HEADLESS_CONFIG . verboseLogging && ! this . _shutdownInProgress ) {
180+ this . log ( "[client#onDisconnect] Attempting reconnection in headless environment..." )
181+ this . handleConnectionFailure ( )
182+ }
56183 }
57184
58185 private onMessage ( data : unknown ) {
59- if ( typeof data !== "object" ) {
60- this . _log ( "[client#onMessage] invalid data" , data )
186+ if ( this . _shutdownInProgress ) {
187+ this . log ( "[client#onMessage] Ignoring message - shutdown in progress" )
61188 return
62189 }
63190
64- const result = ipcMessageSchema . safeParse ( data )
191+ try {
192+ if ( typeof data !== "object" ) {
193+ this . _log ( "[client#onMessage] invalid data" , data )
194+ return
195+ }
65196
66- if ( ! result . success ) {
67- this . log ( "[client#onMessage] invalid payload" , result . error , data )
68- return
69- }
197+ const result = ipcMessageSchema . safeParse ( data )
198+
199+ if ( ! result . success ) {
200+ this . log ( "[client#onMessage] invalid payload" , result . error , data )
201+ return
202+ }
70203
71- const payload = result . data
204+ const payload = result . data
72205
73- if ( payload . origin === IpcOrigin . Server ) {
74- switch ( payload . type ) {
75- case IpcMessageType . Ack :
76- this . _clientId = payload . data . clientId
77- this . emit ( IpcMessageType . Ack , payload . data )
78- break
79- case IpcMessageType . TaskEvent :
80- this . emit ( IpcMessageType . TaskEvent , payload . data )
81- break
206+ if ( payload . origin === IpcOrigin . Server ) {
207+ switch ( payload . type ) {
208+ case IpcMessageType . Ack :
209+ this . _clientId = payload . data . clientId
210+ this . emit ( IpcMessageType . Ack , payload . data )
211+ break
212+ case IpcMessageType . TaskEvent :
213+ this . emit ( IpcMessageType . TaskEvent , payload . data )
214+ break
215+ }
216+ }
217+ } catch ( error ) {
218+ this . log ( `[client#onMessage] Error processing message: ${ error } ` )
219+ if ( HEADLESS_CONFIG . verboseLogging ) {
220+ this . log ( `[client#onMessage] Message data: ${ JSON . stringify ( data ) } ` )
82221 }
83222 }
84223 }
85224
86225 private log ( ...args : unknown [ ] ) {
87- this . _log ( ...args )
226+ // Add timestamp and process info in headless mode
227+ if ( HEADLESS_CONFIG . verboseLogging ) {
228+ const timestamp = new Date ( ) . toISOString ( )
229+ const processInfo = `[PID:${ process . pid } ]`
230+ this . _log ( timestamp , processInfo , ...args )
231+ } else {
232+ this . _log ( ...args )
233+ }
88234 }
89235
90236 public sendCommand ( command : TaskCommand ) {
237+ if ( this . _shutdownInProgress ) {
238+ this . log ( "[client#sendCommand] Cannot send command - shutdown in progress" )
239+ return
240+ }
241+
242+ if ( ! this . _clientId ) {
243+ this . log ( "[client#sendCommand] Cannot send command - no client ID" )
244+ return
245+ }
246+
91247 const message : IpcMessage = {
92248 type : IpcMessageType . TaskCommand ,
93249 origin : IpcOrigin . Client ,
94- clientId : this . _clientId ! ,
250+ clientId : this . _clientId ,
95251 data : command ,
96252 }
97253
98254 this . sendMessage ( message )
99255 }
100256
101257 public sendMessage ( message : IpcMessage ) {
102- ipc . of [ this . _id ] ?. emit ( "message" , message )
258+ if ( this . _shutdownInProgress ) {
259+ this . log ( "[client#sendMessage] Cannot send message - shutdown in progress" )
260+ return
261+ }
262+
263+ try {
264+ const connection = ipc . of [ this . _id ]
265+ if ( connection ) {
266+ connection . emit ( "message" , message )
267+ } else {
268+ this . log ( "[client#sendMessage] IPC connection not available" )
269+ }
270+ } catch ( error ) {
271+ this . log ( `[client#sendMessage] Error sending message: ${ error } ` )
272+ }
103273 }
104274
105275 public disconnect ( ) {
106276 try {
107- ipc . disconnect ( this . _id )
108- // @TODO : Should we set _disconnect here?
277+ this . _isConnected = false
278+ this . _clientId = undefined
279+
280+ if ( this . _connectionTimeout ) {
281+ clearTimeout ( this . _connectionTimeout )
282+ this . _connectionTimeout = undefined
283+ }
284+
285+ if ( ipc . of [ this . _id ] ) {
286+ ipc . disconnect ( this . _id )
287+ }
109288 } catch ( error ) {
110289 this . log ( "[client#disconnect] error disconnecting" , error )
111290 }
112291 }
113292
293+ public async shutdown ( ) : Promise < void > {
294+ this . log ( "[IpcClient] Starting graceful shutdown..." )
295+
296+ try {
297+ this . _shutdownInProgress = true
298+
299+ // Clear connection timeout
300+ if ( this . _connectionTimeout ) {
301+ clearTimeout ( this . _connectionTimeout )
302+ this . _connectionTimeout = undefined
303+ }
304+
305+ // Disconnect from server
306+ this . disconnect ( )
307+
308+ this . log ( "[IpcClient] Graceful shutdown completed" )
309+ } catch ( error ) {
310+ this . log ( `[IpcClient] Error during shutdown: ${ error } ` )
311+ throw error
312+ }
313+ }
314+
114315 public get socketPath ( ) {
115316 return this . _socketPath
116317 }
0 commit comments