@@ -117,8 +117,17 @@ export const useSessionSharing = ({
117117 currentUserRef . current = currentUser ;
118118 } , [ currentUser ] ) ;
119119
120- // Track processed message IDs to prevent duplicates - ALWAYS call this hook
121- const processedMessageIds = useRef < Set < string > > ( new Set ( ) ) ;
120+ // Track processed messages to prevent duplicates - ALWAYS call this hook
121+ const processedMessages = useRef < Set < string > > ( new Set ( ) ) ;
122+
123+ // Helper function to create a deduplication key based on content and timestamp
124+ const createMessageKey = ( content : string , sender : string , timestamp ?: number ) => {
125+ const time = timestamp || Date . now ( ) ;
126+ // Round timestamp to nearest second to catch near-simultaneous duplicates
127+ const roundedTime = Math . floor ( time / 1000 ) ;
128+ // Use first 50 chars of content + sender + rounded timestamp
129+ return `${ sender } -${ roundedTime } -${ content . substring ( 0 , 50 ) } ` ;
130+ } ;
122131
123132 // Listen for session-related Matrix messages
124133 useEffect ( ( ) => {
@@ -132,8 +141,18 @@ export const useSessionSharing = ({
132141 sessionId,
133142 roomId : stateRef . current . roomId ,
134143 isShared : stateRef . current . isShared ,
135- participantsCount : stateRef . current . participants . length
144+ participantsCount : stateRef . current . participants . length ,
145+ onMessageSyncAvailable : ! ! onMessageSync
136146 } ) ;
147+
148+ // Debug: Test the onMessageSync callback immediately
149+ if ( onMessageSync ) {
150+ console . log ( '🔧 useSessionSharing: Testing onMessageSync callback...' ) ;
151+ // Don't actually call it, just confirm it exists
152+ console . log ( '🔧 useSessionSharing: onMessageSync callback is available and callable' ) ;
153+ } else {
154+ console . warn ( '⚠️ useSessionSharing: onMessageSync callback is NOT available!' ) ;
155+ }
137156
138157 const handleSessionMessage = ( data : any ) => {
139158 const { content, sender, roomId, senderInfo } = data ;
@@ -215,7 +234,9 @@ export const useSessionSharing = ({
215234 isFromCurrentRoom,
216235 isSessionMatch,
217236 shouldProcessMessage,
218- sender
237+ sender,
238+ messageRole : messageData . role ,
239+ messageContent : messageData . content ?. substring ( 0 , 50 ) + '...'
219240 } ) ;
220241
221242 if ( shouldProcessMessage ) {
@@ -239,25 +260,79 @@ export const useSessionSharing = ({
239260 }
240261 }
241262
263+ // Enhanced role detection for session messages
264+ let finalRole = messageData . role as 'user' | 'assistant' ;
265+
266+ // If the role is 'assistant', double-check that it's actually from a Goose instance
267+ if ( finalRole === 'assistant' ) {
268+ const isFromGoose = senderData ?. displayName ?. toLowerCase ( ) . includes ( 'goose' ) ||
269+ senderData ?. userId ?. toLowerCase ( ) . includes ( 'goose' ) ||
270+ messageData . content ?. includes ( '🦆' ) ||
271+ messageData . content ?. includes ( '🤖' ) ;
272+
273+ if ( ! isFromGoose ) {
274+ console . log ( '🔍 Role correction: Message marked as assistant but not from Goose, changing to user' ) ;
275+ finalRole = 'user' ;
276+ }
277+ }
278+
279+ // If the role is 'user' but content looks like a Goose response, correct it
280+ if ( finalRole === 'user' ) {
281+ const looksLikeGooseResponse = messageData . content && (
282+ messageData . content . includes ( '🦆' ) ||
283+ messageData . content . includes ( '🤖' ) ||
284+ messageData . content . startsWith ( 'I\'m' ) ||
285+ messageData . content . includes ( 'I can help' ) ||
286+ messageData . content . includes ( 'Let me' ) ||
287+ ( messageData . content . length > 100 && messageData . content . includes ( '\n\n' ) ) ||
288+ / ` ` ` [ \s \S ] * ` ` ` / . test ( messageData . content ) // Contains code blocks
289+ ) ;
290+
291+ const isFromGoose = senderData ?. displayName ?. toLowerCase ( ) . includes ( 'goose' ) ||
292+ senderData ?. userId ?. toLowerCase ( ) . includes ( 'goose' ) ;
293+
294+ if ( looksLikeGooseResponse || isFromGoose ) {
295+ console . log ( '🔍 Role correction: Message marked as user but looks like Goose response, changing to assistant' ) ;
296+ finalRole = 'assistant' ;
297+ }
298+ }
299+
242300 // Convert to local message format with proper sender attribution
243301 const message : Message = {
244302 id : `shared-${ Date . now ( ) } -${ Math . random ( ) . toString ( 36 ) . substr ( 2 , 9 ) } ` ,
245- role : messageData . role ,
303+ role : finalRole ,
246304 created : Math . floor ( Date . now ( ) / 1000 ) ,
247305 content : [ {
248306 type : 'text' ,
249307 text : messageData . content ,
250308 } ] ,
251309 sender : senderData , // Include sender information
310+ metadata : {
311+ originalRole : messageData . role ,
312+ correctedRole : finalRole ,
313+ isFromMatrix : true ,
314+ skipLocalResponse : true , // Prevent triggering local AI response
315+ preventAutoResponse : true ,
316+ isFromCollaborator : true ,
317+ sessionMessageId : messageData . sessionId
318+ }
252319 } ;
253320
254- console . log ( '💬 Syncing message to local session with sender:' , message ) ;
321+ console . log ( '💬 Syncing session message to local session:' , {
322+ messageId : message . id ,
323+ originalRole : messageData . role ,
324+ finalRole : finalRole ,
325+ sender : senderData ?. displayName || senderData ?. userId ,
326+ content : messageData . content ?. substring ( 0 , 50 ) + '...'
327+ } ) ;
328+
255329 onMessageSync ?.( message ) ;
256330 } else {
257331 console . log ( '🚫 Skipping session message - not from current room/session' ) ;
258332 }
259333 } catch ( error ) {
260334 console . error ( 'Failed to parse session message:' , error ) ;
335+ console . error ( 'Raw content that failed to parse:' , content ) ;
261336 }
262337 }
263338 } ;
@@ -269,18 +344,16 @@ export const useSessionSharing = ({
269344 const currentState = stateRef . current ;
270345 const currentUserFromRef = currentUserRef . current ;
271346
272- // Create a unique message ID for deduplication
273- const messageId = event ?. getId ?.( ) || `${ sender } -${ timestamp ?. getTime ?.( ) || Date . now ( ) } -${ content ?. substring ( 0 , 20 ) } ` ;
274-
275- // Check if we've already processed this message
276- if ( processedMessageIds . current . has ( messageId ) ) {
277- console . log ( '🚫 Skipping duplicate message:' , messageId ) ;
347+ // Simple deduplication: check if we've seen this exact message content at this time
348+ const messageKey = createMessageKey ( content || '' , sender , timestamp ?. getTime ?.( ) ) ;
349+ if ( processedMessages . current . has ( messageKey ) ) {
350+ console . log ( '🚫 Skipping duplicate regular message - same content and time:' , messageKey ) ;
278351 return ;
279352 }
280353
281354 // Debug logging for all incoming messages to understand the flow
282355 console . log ( '🔍 handleRegularMessage called:' , {
283- messageId ,
356+ messageKey ,
284357 content : content ?. substring ( 0 , 50 ) + '...' ,
285358 sender,
286359 roomId,
@@ -291,16 +364,16 @@ export const useSessionSharing = ({
291364
292365 // Only process messages from Matrix rooms that are part of our session
293366 if ( currentState . roomId && roomId === currentState . roomId && sender !== currentUserFromRef ?. userId ) {
294- console . log ( '💬 Processing message in session room:' , { messageId , content, sender, roomId, senderInfo } ) ;
367+ console . log ( '💬 Processing message in session room:' , { messageKey , content, sender, roomId, senderInfo } ) ;
295368
296- // Skip if this is a goose-session-message (should be handled by handleSessionMessage )
369+ // Skip if this is a goose-session-message (should be handled by handleGooseSessionSync )
297370 if ( content && content . includes ( 'goose-session-message:' ) ) {
298- console . log ( '🚫 Skipping handleRegularMessage - this is a session message, will be handled by handleSessionMessage ' ) ;
371+ console . log ( '🚫 Skipping handleRegularMessage - this is a session message, will be handled by handleGooseSessionSync ' ) ;
299372 return ;
300373 }
301374
302375 // Mark this message as processed
303- processedMessageIds . current . add ( messageId ) ;
376+ processedMessages . current . add ( messageKey ) ;
304377
305378 // Find sender info from friends or participants
306379 let senderData = senderInfo ;
@@ -430,15 +503,147 @@ export const useSessionSharing = ({
430503 if ( currentState . roomId && roomId === currentState . roomId && sender !== currentUserFromRef ?. userId ) {
431504 console . log ( '🔄 Processing gooseSessionSync message in session room:' , { content, sender, roomId, senderInfo } ) ;
432505
433- // Skip if this is already a goose-session-message (to avoid double processing)
506+ // If this is a goose-session-message, process it here since handleSessionMessage isn't being called
434507 if ( content && content . includes ( 'goose-session-message:' ) ) {
435- console . log ( '🚫 Skipping gooseSessionSync - already a session message, will be handled by handleSessionMessage' ) ;
436- return ;
508+ console . log ( '🔄 Processing goose-session-message in gooseSessionSync handler' ) ;
509+
510+ // Call the same logic as handleSessionMessage for session messages
511+ try {
512+ const messageData = JSON . parse ( content . split ( 'goose-session-message:' ) [ 1 ] ) ;
513+
514+ // Simple deduplication: check if we've seen this exact message content at this time
515+ const messageKey = createMessageKey ( messageData . content || '' , sender , messageData . timestamp ) ;
516+ if ( processedMessages . current . has ( messageKey ) ) {
517+ console . log ( '🚫 Skipping duplicate message - same content and time:' , messageKey ) ;
518+ return ;
519+ }
520+
521+ // Mark this message as processed
522+ processedMessages . current . add ( messageKey ) ;
523+
524+ // In Matrix collaboration, we want to process session messages from the current room only
525+ const isMatrixRoom = sessionId && sessionId . startsWith ( '!' ) ;
526+ const isFromCurrentRoom = ! roomId || roomId === sessionId ;
527+ const isSessionMatch = messageData . sessionId === sessionId ;
528+
529+ // For Matrix rooms, prioritize room ID matching over session ID matching
530+ const shouldProcessMessage = isMatrixRoom ? isFromCurrentRoom : ( isSessionMatch || isFromCurrentRoom ) ;
531+
532+ console . log ( '🔍 Session message processing check (gooseSessionSync):' , {
533+ messageSessionId : messageData . sessionId ,
534+ currentSessionId : sessionId ,
535+ messageRoomId : roomId ,
536+ isMatrixRoom,
537+ isFromCurrentRoom,
538+ isSessionMatch,
539+ shouldProcessMessage,
540+ sender,
541+ messageRole : messageData . role ,
542+ messageContent : messageData . content ?. substring ( 0 , 50 ) + '...'
543+ } ) ;
544+
545+ if ( shouldProcessMessage ) {
546+ // Get sender information for proper attribution
547+ let senderData = senderInfo ;
548+ if ( ! senderData && sender ) {
549+ // Try to find sender in friends list
550+ const friend = friendsRef . current . find ( f => f . userId === sender ) ;
551+ if ( friend ) {
552+ senderData = {
553+ userId : friend . userId ,
554+ displayName : friend . displayName ,
555+ avatarUrl : friend . avatarUrl ,
556+ } ;
557+ } else {
558+ // Fallback to basic sender info from Matrix ID
559+ senderData = {
560+ userId : sender ,
561+ displayName : sender . split ( ':' ) [ 0 ] . substring ( 1 ) , // Extract username from Matrix ID
562+ } ;
563+ }
564+ }
565+
566+ // Enhanced role detection for session messages
567+ let finalRole = messageData . role as 'user' | 'assistant' ;
568+
569+ // If the role is 'assistant', double-check that it's actually from a Goose instance
570+ if ( finalRole === 'assistant' ) {
571+ const isFromGoose = senderData ?. displayName ?. toLowerCase ( ) . includes ( 'goose' ) ||
572+ senderData ?. userId ?. toLowerCase ( ) . includes ( 'goose' ) ||
573+ messageData . content ?. includes ( '🦆' ) ||
574+ messageData . content ?. includes ( '🤖' ) ;
575+
576+ if ( ! isFromGoose ) {
577+ console . log ( '🔍 Role correction: Message marked as assistant but not from Goose, changing to user' ) ;
578+ finalRole = 'user' ;
579+ }
580+ }
581+
582+ // If the role is 'user' but content looks like a Goose response, correct it
583+ if ( finalRole === 'user' ) {
584+ const looksLikeGooseResponse = messageData . content && (
585+ messageData . content . includes ( '🦆' ) ||
586+ messageData . content . includes ( '🤖' ) ||
587+ messageData . content . startsWith ( 'I\'m' ) ||
588+ messageData . content . includes ( 'I can help' ) ||
589+ messageData . content . includes ( 'Let me' ) ||
590+ ( messageData . content . length > 100 && messageData . content . includes ( '\n\n' ) ) ||
591+ / ` ` ` [ \s \S ] * ` ` ` / . test ( messageData . content ) // Contains code blocks
592+ ) ;
593+
594+ const isFromGoose = senderData ?. displayName ?. toLowerCase ( ) . includes ( 'goose' ) ||
595+ senderData ?. userId ?. toLowerCase ( ) . includes ( 'goose' ) ;
596+
597+ if ( looksLikeGooseResponse || isFromGoose ) {
598+ console . log ( '🔍 Role correction: Message marked as user but looks like Goose response, changing to assistant' ) ;
599+ finalRole = 'assistant' ;
600+ }
601+ }
602+
603+ // Convert to local message format with proper sender attribution
604+ const message : Message = {
605+ id : `shared-${ Date . now ( ) } -${ Math . random ( ) . toString ( 36 ) . substr ( 2 , 9 ) } ` ,
606+ role : finalRole ,
607+ created : Math . floor ( Date . now ( ) / 1000 ) ,
608+ content : [ {
609+ type : 'text' ,
610+ text : messageData . content ,
611+ } ] ,
612+ sender : senderData , // Include sender information
613+ metadata : {
614+ originalRole : messageData . role ,
615+ correctedRole : finalRole ,
616+ isFromMatrix : true ,
617+ skipLocalResponse : true , // Prevent triggering local AI response
618+ preventAutoResponse : true ,
619+ isFromCollaborator : true ,
620+ sessionMessageId : messageData . sessionId
621+ }
622+ } ;
623+
624+ console . log ( '💬 *** PROCESSING SESSION MESSAGE IN GOOSE SESSION SYNC ***:' , {
625+ messageId : message . id ,
626+ originalRole : messageData . role ,
627+ finalRole : finalRole ,
628+ sender : senderData ?. displayName || senderData ?. userId ,
629+ content : messageData . content ?. substring ( 0 , 50 ) + '...'
630+ } ) ;
631+
632+ console . log ( '💬 *** CALLING onMessageSync FROM GOOSE SESSION SYNC ***' ) ;
633+ onMessageSync ?.( message ) ;
634+ } else {
635+ console . log ( '🚫 Skipping session message - not from current room/session (gooseSessionSync)' ) ;
636+ }
637+ } catch ( error ) {
638+ console . error ( 'Failed to parse session message in gooseSessionSync:' , error ) ;
639+ console . error ( 'Raw content that failed to parse:' , content ) ;
640+ }
641+
642+ return ; // Exit early after processing session message
437643 }
438644
439- // DON'T call handleRegularMessage here - it will be handled by the regular message handler
440- // This was causing duplicate messages because both handlers were processing the same message
441- console . log ( '🚫 Skipping gooseSessionSync processing - regular message handler will process this to avoid duplication' ) ;
645+ // For non-session messages, let the regular message handler process them
646+ console . log ( '🔄 Non-session message in gooseSessionSync - letting regular handler process' ) ;
442647 } else {
443648 console . log ( '🚫 Skipping gooseSessionSync - not from current session room or from self' ) ;
444649 }
0 commit comments