@@ -32,13 +32,25 @@ if (!process.env.GH_TOKEN) {
3232 process . exit ( 1 ) ;
3333}
3434
35- // Initialize Discord client
35+ // Self-healing configuration
36+ const RECONNECT_DELAY = 5000 ; // 5 seconds
37+ const MAX_RECONNECT_ATTEMPTS = 10 ;
38+ let reconnectAttempts = 0 ;
39+ let isReconnecting = false ;
40+
41+ // Initialize Discord client with error handling
3642const client = new Client ( {
3743 intents : [
3844 GatewayIntentBits . Guilds ,
3945 GatewayIntentBits . GuildMessages ,
4046 GatewayIntentBits . MessageContent ,
4147 ] ,
48+ // Add WebSocket options for better error handling
49+ ws : {
50+ properties : {
51+ browser : 'Discord.js'
52+ }
53+ }
4254} ) ;
4355
4456
@@ -183,101 +195,141 @@ Be precise, actionable, and concise. Users value speed and accuracy over verbose
183195 let stderrBuffer = "" ;
184196 let extractedSessionId = sessionId ; // Keep track of session ID
185197 let threadTitle = null ;
198+ let jsonBuffer = "" ; // Buffer to accumulate partial JSON
186199
187200 // Process stdout stream in real-time
188201 try {
189202 for await ( const chunk of proc . stdout ) {
190203 const text = new TextDecoder ( ) . decode ( chunk ) ;
191-
192- // Parse each line as separate JSON objects
193- const lines = text . split ( '\n' ) . filter ( line => line . trim ( ) ) ;
194-
195- for ( const line of lines ) {
196- try {
197- const jsonData = JSON . parse ( line ) ;
198-
199- // Extract session ID from metadata
200- if ( jsonData . type === 'metadata' && jsonData . session_id ) {
201- extractedSessionId = jsonData . session_id ;
202- console . log ( `Captured session ID: ${ extractedSessionId } ` ) ;
203- }
204-
205- // Send assistant messages immediately as they arrive
206- if ( jsonData . type === 'assistant' && jsonData . message ?. content ) {
207- const content = Array . isArray ( jsonData . message . content )
208- ? jsonData . message . content . map ( block => {
209- if ( typeof block === 'string' ) return block ;
210- if ( block . text ) return block . text ;
211- // Skip non-text blocks (like tool_use blocks)
212- return '' ;
213- } ) . join ( '' )
214- : jsonData . message . content ;
215-
216- if ( content ) {
217- responseContent += content ;
218-
219- // Extract thread title if present (but don't show to user)
220- const titleMatch = content . match ( / \[ T H R E A D _ T I T L E : \s * ( [ ^ \] ] + ) \] / ) ;
221- if ( titleMatch && ! threadTitle ) {
222- threadTitle = titleMatch [ 1 ] . trim ( ) ;
223- console . log ( `Extracted thread title: ${ threadTitle } ` ) ;
224- }
225-
226- // Remove thread title from content before sending to user
227- const userContent = content . replace ( / \[ T H R E A D _ T I T L E : \s * [ ^ \] ] + \] / g, '' ) . trim ( ) ;
228-
229- // Send each chunk as a new message instead of editing
204+ jsonBuffer += text ;
205+
206+ // Try to extract complete JSON objects from buffer
207+ let startIndex = 0 ;
208+ let braceCount = 0 ;
209+ let inString = false ;
210+ let escapeNext = false ;
211+
212+ for ( let i = 0 ; i < jsonBuffer . length ; i ++ ) {
213+ const char = jsonBuffer [ i ] ;
214+
215+ if ( escapeNext ) {
216+ escapeNext = false ;
217+ continue ;
218+ }
219+
220+ if ( char === '\\' && inString ) {
221+ escapeNext = true ;
222+ continue ;
223+ }
224+
225+ if ( char === '"' ) {
226+ inString = ! inString ;
227+ continue ;
228+ }
229+
230+ if ( ! inString ) {
231+ if ( char === '{' ) {
232+ braceCount ++ ;
233+ } else if ( char === '}' ) {
234+ braceCount -- ;
235+
236+ // Complete JSON object found
237+ if ( braceCount === 0 ) {
238+ const jsonStr = jsonBuffer . substring ( startIndex , i + 1 ) ;
230239 try {
231- if ( userContent ) {
232- await channel . send ( userContent ) ;
233- lastMessageRef = true ; // Just track that we've sent something
240+ const jsonData = JSON . parse ( jsonStr ) ;
241+
242+ // Extract session ID from metadata
243+ if ( jsonData . type === 'metadata' && jsonData . session_id ) {
244+ extractedSessionId = jsonData . session_id ;
245+ console . log ( `Captured session ID: ${ extractedSessionId } ` ) ;
234246 }
235- } catch ( discordError ) {
236- console . error ( "Discord update error:" , discordError ) ;
237- }
238- }
239- }
240247
241- // Handle final result
242- if ( jsonData . type === 'result' && jsonData . subtype === 'success' ) {
243- if ( jsonData . result ) {
244- responseContent = jsonData . result ;
248+ // Send assistant messages immediately as they arrive
249+ if ( jsonData . type === 'assistant' && jsonData . message ?. content ) {
250+ const content = Array . isArray ( jsonData . message . content )
251+ ? jsonData . message . content . map ( block => {
252+ if ( typeof block === 'string' ) return block ;
253+ if ( block . text ) return block . text ;
254+ // Skip non-text blocks (like tool_use blocks)
255+ return '' ;
256+ } ) . join ( '' )
257+ : jsonData . message . content ;
258+
259+ if ( content ) {
260+ responseContent += content ;
261+
262+ // Extract thread title if present (but don't show to user)
263+ const titleMatch = content . match ( / \[ T H R E A D _ T I T L E : \s * ( [ ^ \] ] + ) \] / ) ;
264+ if ( titleMatch && ! threadTitle ) {
265+ threadTitle = titleMatch [ 1 ] . trim ( ) ;
266+ console . log ( `Extracted thread title: ${ threadTitle } ` ) ;
267+ }
268+
269+ // Remove thread title from content before sending to user
270+ const userContent = content . replace ( / \[ T H R E A D _ T I T L E : \s * [ ^ \] ] + \] / g, '' ) . trim ( ) ;
271+
272+ // Send each chunk as a new message instead of editing
273+ try {
274+ if ( userContent ) {
275+ await channel . send ( userContent ) ;
276+ lastMessageRef = true ; // Just track that we've sent something
277+ }
278+ } catch ( discordError ) {
279+ console . error ( "Discord update error:" , discordError ) ;
280+ }
281+ }
282+ }
245283
246- // Extract thread title from final result if not already found
247- const titleMatch = responseContent . match ( / \[ T H R E A D _ T I T L E : \s * ( [ ^ \] ] + ) \] / ) ;
248- if ( titleMatch && ! threadTitle ) {
249- threadTitle = titleMatch [ 1 ] . trim ( ) ;
250- console . log ( `Extracted thread title from result: ${ threadTitle } ` ) ;
251- }
284+ // Handle final result
285+ if ( jsonData . type === 'result' && jsonData . subtype === 'success' ) {
286+ if ( jsonData . result ) {
287+ responseContent = jsonData . result ;
288+
289+ // Extract thread title from final result if not already found
290+ const titleMatch = responseContent . match ( / \[ T H R E A D _ T I T L E : \s * ( [ ^ \] ] + ) \] / ) ;
291+ if ( titleMatch && ! threadTitle ) {
292+ threadTitle = titleMatch [ 1 ] . trim ( ) ;
293+ console . log ( `Extracted thread title from result: ${ threadTitle } ` ) ;
294+ }
295+
296+ // Only send final result if we haven't sent streaming messages
297+ try {
298+ if ( ! lastMessageRef ) {
299+ // Remove thread title from final response before sending to user
300+ const userResponse = responseContent . replace ( / \[ T H R E A D _ T I T L E : \s * [ ^ \] ] + \] / g, '' ) . trim ( ) ;
301+ if ( userResponse ) {
302+ await channel . send ( userResponse ) ;
303+ lastMessageRef = true ;
304+ }
305+ }
306+ // If we were streaming, the final result is already incorporated
307+ } catch ( discordError ) {
308+ console . error ( "Discord final update error:" , discordError ) ;
309+ }
310+ }
252311
253- // Only send final result if we haven't sent streaming messages
254- try {
255- if ( ! lastMessageRef ) {
256- // Remove thread title from final response before sending to user
257- const userResponse = responseContent . replace ( / \[ T H R E A D _ T I T L E : \s * [ ^ \] ] + \] / g, '' ) . trim ( ) ;
258- if ( userResponse ) {
259- await channel . send ( userResponse ) ;
260- lastMessageRef = true ;
312+ // Also capture session ID from result if available
313+ if ( jsonData . session_id && ! extractedSessionId ) {
314+ extractedSessionId = jsonData . session_id ;
315+ console . log ( `Captured session ID from result: ${ extractedSessionId } ` ) ;
261316 }
262317 }
263- // If we were streaming, the final result is already incorporated
264- } catch ( discordError ) {
265- console . error ( "Discord final update error:" , discordError ) ;
266- }
267- }
268318
269- // Also capture session ID from result if available
270- if ( jsonData . session_id && ! extractedSessionId ) {
271- extractedSessionId = jsonData . session_id ;
272- console . log ( `Captured session ID from result: ${ extractedSessionId } ` ) ;
319+ } catch ( parseError ) {
320+ console . log ( "Failed to parse JSON object:" , parseError . message ) ;
321+ }
322+
323+ // Move to next potential JSON object
324+ startIndex = i + 1 ;
325+ braceCount = 0 ;
273326 }
274327 }
275-
276- } catch ( parseError ) {
277- // Skip invalid JSON lines
278- console . log ( "Skipping non-JSON line:" , line . substring ( 0 , 100 ) ) ;
279328 }
280329 }
330+
331+ // Remove processed JSON objects from buffer
332+ jsonBuffer = jsonBuffer . substring ( startIndex ) ;
281333 }
282334 } catch ( streamError ) {
283335 console . error ( "Error processing stdout stream:" , streamError ) ;
0 commit comments