Skip to content

Commit 7058ea7

Browse files
committed
Implement hybrid session management system for Matrix chat
Core Implementation: - Created SessionMappingService to manage Matrix room ID ↔ Goose session ID relationships - Added automatic session mapping creation when joining/creating Matrix rooms - Updated useChatEngine and useMessageStream to use mapped Goose session IDs for backend calls - Matrix room IDs still used for Matrix operations, Goose session IDs for backend APIs Key Features: - Automatic Goose session ID generation (YYYYMMDD_HHMMSS format) - Persistent mapping storage in localStorage with cleanup of old mappings - Session mapping ensures backend compatibility for token counting and SSE streams - Proper session persistence and reload functionality - Comprehensive logging for debugging session ID mapping Backend Integration: - Session token fetching now uses mapped Goose session IDs - SSE stream session handling uses mapped session IDs - Matrix room operations continue using Matrix room IDs - Eliminates 'Session not found' errors for Matrix collaborative sessions This resolves the fundamental incompatibility between Matrix room IDs and Goose session IDs, enabling full backend functionality while preserving Matrix collaborative features.
1 parent d93bad4 commit 7058ea7

File tree

4 files changed

+339
-16
lines changed

4 files changed

+339
-16
lines changed

ui/desktop/src/hooks/useChatEngine.ts

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
import { ChatType } from '../types/chat';
1717
import { ChatState } from '../types/chatState';
1818
import { getSession } from '../api';
19+
import { sessionMappingService } from '../services/SessionMappingService';
1920

2021
// Force rebuild timestamp: 2025-01-15T02:35:00Z - Fixed Matrix session token fetching
2122

@@ -75,6 +76,15 @@ export const useChatEngine = ({
7576
}
7677
}, [powerSaveTimeoutId]);
7778

79+
// Get the appropriate session ID for backend API calls
80+
const backendSessionId = sessionMappingService.getBackendSessionId(chat.sessionId);
81+
82+
console.log('📋 useChatEngine: Session ID mapping:', {
83+
originalSessionId: chat.sessionId,
84+
backendSessionId,
85+
isMatrixSession: chat.sessionId.startsWith('!'),
86+
});
87+
7888
const {
7989
messages,
8090
append: originalAppend,
@@ -91,10 +101,10 @@ export const useChatEngine = ({
91101
setError,
92102
} = useMessageStream({
93103
api: getApiUrl('/reply'),
94-
id: chat.sessionId,
104+
id: chat.sessionId, // Keep original ID for frontend state management
95105
initialMessages: chat.messages,
96106
body: {
97-
session_id: chat.sessionId,
107+
session_id: backendSessionId, // Use mapped session ID for backend
98108
session_working_dir: window.appConfig.get('GOOSE_WORKING_DIR'),
99109
...(chat.recipeConfig?.title
100110
? {
@@ -236,8 +246,17 @@ export const useChatEngine = ({
236246
useEffect(() => {
237247
const fetchSessionTokens = async () => {
238248
try {
249+
// Use session mapping service to get the appropriate backend session ID
250+
const backendSessionId = sessionMappingService.getBackendSessionId(chat.sessionId);
251+
252+
console.log('📋 Fetching session tokens:', {
253+
originalSessionId: chat.sessionId,
254+
backendSessionId,
255+
isMatrixSession: chat.sessionId.startsWith('!'),
256+
});
257+
239258
const response = await getSession<true>({
240-
path: { session_id: chat.sessionId },
259+
path: { session_id: backendSessionId },
241260
throwOnError: true,
242261
});
243262
const sessionDetails = response.data;
@@ -249,13 +268,8 @@ export const useChatEngine = ({
249268
}
250269
};
251270

252-
// Skip session token fetching for Matrix sessions (room IDs start with '!')
253-
// Matrix sessions don't have traditional Goose session data on the backend
254-
const isMatrixSession = chat.sessionId && chat.sessionId.startsWith('!');
255-
256271
// Only fetch session tokens when chat state is idle to avoid resetting during streaming
257-
// and when it's not a Matrix session
258-
if (chat.sessionId && chatState === ChatState.Idle && !isMatrixSession) {
272+
if (chat.sessionId && chatState === ChatState.Idle) {
259273
fetchSessionTokens();
260274
}
261275
}, [chat.sessionId, messages, chatState]);

ui/desktop/src/hooks/useMessageStream.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import useSWR from 'swr';
33
import { createUserMessage, hasCompletedToolCalls, Message, Role } from '../types/message';
44
import { getSession, Session } from '../api';
55
import { ChatState } from '../types/chatState';
6+
import { sessionMappingService } from '../services/SessionMappingService';
67

78
// Force rebuild timestamp: 2025-01-15T02:35:00Z - Fixed Matrix session SSE stream error
89

@@ -343,14 +344,19 @@ export function useMessageStream({
343344
const sessionId = (extraMetadataRef.current.body as Record<string, unknown>)
344345
?.session_id as string;
345346

346-
// Skip session fetching for Matrix sessions (room IDs start with '!')
347-
// Matrix sessions don't have traditional Goose session data on the backend
348-
const isMatrixSession = sessionId && sessionId.startsWith('!');
349-
350-
if (sessionId && !isMatrixSession) {
347+
if (sessionId) {
351348
try {
349+
// Use session mapping service to get the appropriate backend session ID
350+
const backendSessionId = sessionMappingService.getBackendSessionId(sessionId);
351+
352+
console.log('📋 Fetching session data in SSE Finish:', {
353+
originalSessionId: sessionId,
354+
backendSessionId,
355+
isMatrixSession: sessionId.startsWith('!'),
356+
});
357+
352358
const sessionResponse = await getSession({
353-
path: { session_id: sessionId },
359+
path: { session_id: backendSessionId },
354360
throwOnError: true,
355361
});
356362

@@ -359,7 +365,7 @@ export function useMessageStream({
359365
}
360366
} catch (err) {
361367
console.error('Error fetching session data:', err);
362-
// Don't throw here, just log the error for Matrix sessions
368+
// Don't throw here, just log the error
363369
}
364370
}
365371
break;

ui/desktop/src/services/MatrixService.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as sdk from 'matrix-js-sdk';
22
import { EventEmitter } from 'events';
3+
import { sessionMappingService, SessionMappingService } from './SessionMappingService';
34

45
export interface MatrixUser {
56
userId: string;
@@ -783,6 +784,17 @@ export class MatrixService extends EventEmitter {
783784
// We'll use regular message types instead of custom ones
784785
});
785786

787+
// Create session mapping for this Matrix room
788+
const participants = [this.config.userId!, ...inviteUserIds];
789+
const mapping = sessionMappingService.createMapping(room.room_id, participants, name);
790+
791+
console.log('📋 Created AI session with mapping:', {
792+
matrixRoomId: room.room_id,
793+
gooseSessionId: mapping.gooseSessionId,
794+
participants: participants.length,
795+
name,
796+
});
797+
786798
return room.room_id;
787799
}
788800

@@ -812,6 +824,9 @@ export class MatrixService extends EventEmitter {
812824
const existingRoom = this.client.getRoom(roomId);
813825
if (existingRoom && existingRoom.getMyMembership() === 'join') {
814826
console.log('✅ Already joined room:', roomId);
827+
828+
// Still ensure session mapping exists for this room
829+
this.ensureSessionMapping(roomId, existingRoom);
815830
return;
816831
}
817832

@@ -823,6 +838,12 @@ export class MatrixService extends EventEmitter {
823838
this.cachedRooms = null;
824839
this.cachedFriends = null;
825840

841+
// Get the room after joining to create session mapping
842+
const joinedRoom = this.client.getRoom(roomId);
843+
if (joinedRoom) {
844+
this.ensureSessionMapping(roomId, joinedRoom);
845+
}
846+
826847
// Emit join event
827848
this.emit('roomJoined', { roomId });
828849

@@ -856,6 +877,35 @@ export class MatrixService extends EventEmitter {
856877
}
857878
}
858879

880+
/**
881+
* Ensure a session mapping exists for a Matrix room
882+
*/
883+
private ensureSessionMapping(roomId: string, room: any): void {
884+
// Check if mapping already exists
885+
const existingMapping = sessionMappingService.getMapping(roomId);
886+
if (existingMapping) {
887+
console.log('📋 Session mapping already exists for room:', roomId, '→', existingMapping.gooseSessionId);
888+
889+
// Update participants if needed
890+
const currentParticipants = room.getMembers().map((member: any) => member.userId);
891+
sessionMappingService.updateParticipants(roomId, currentParticipants);
892+
return;
893+
}
894+
895+
// Create new mapping if none exists
896+
const participants = room.getMembers().map((member: any) => member.userId);
897+
const roomName = room.name || `Matrix Room ${roomId.substring(1, 8)}`;
898+
899+
const mapping = sessionMappingService.createMapping(roomId, participants, roomName);
900+
901+
console.log('📋 Created session mapping for joined room:', {
902+
matrixRoomId: roomId,
903+
gooseSessionId: mapping.gooseSessionId,
904+
participants: participants.length,
905+
roomName,
906+
});
907+
}
908+
859909
/**
860910
* Get all rooms the user is in
861911
*/

0 commit comments

Comments
 (0)