fix(chat): fix old session resume losing server session ID
* fix(chat): reset agent typing status when resuming old sessions * fix(chat): fix race condition in websocket request registration * test(chat): update mocks for new send/sendMessage onSent callback * style(chat): use named onSent param instead of trailing lambda * style(chat): fix import ordering for ConcurrentHashMap * test(chat): fix remaining 2-arg verify calls for send()
This commit is contained in:
3 files changed
+72
-35
No files matched your search
@@ -136,8 +136,10 @@ object HermesWsClient {
|
||||
fun send(
|
||||
method: String,
|
||||
params: Map<String, Any> = emptyMap(),
|
||||
onSent: ((String) -> Unit)? = null,
|
||||
): String {
|
||||
val id = requestId.incrementAndGet().toString()
|
||||
onSent?.invoke(id)
|
||||
val request = JsonRpcRequest(id = id, method = method, params = params)
|
||||
val json = gson.toJson(request)
|
||||
// B2 (Jun 18 2026, kanban t_8884db16): outgoing JSON contains user
|
||||
@@ -152,10 +154,12 @@ object HermesWsClient {
|
||||
fun sendMessage(
|
||||
sessionId: String,
|
||||
text: String,
|
||||
onSent: ((String) -> Unit)? = null,
|
||||
): String =
|
||||
send(
|
||||
method = WsMethods.PROMPT_SUBMIT,
|
||||
params = mapOf("session_id" to sessionId, "text" to text),
|
||||
onSent = onSent,
|
||||
)
|
||||
|
||||
// ── Internal ─────────────────────────────────────────────────────────
|
||||
|
||||
@@ -21,6 +21,7 @@ import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
data class ChatUiState(
|
||||
val messages: List<ChatMessage> = emptyList(),
|
||||
@@ -60,7 +61,7 @@ class ChatViewModel(
|
||||
val uiState: StateFlow<ChatUiState> = _uiState.asStateFlow()
|
||||
|
||||
private val wsClient = HermesWsClient
|
||||
private val pendingRequests = mutableMapOf<String, String>() // requestId -> method
|
||||
private val pendingRequests = ConcurrentHashMap<String, String>() // requestId -> method
|
||||
private val dao = HermesDatabase.get(application).chatMessageDao()
|
||||
|
||||
init {
|
||||
@@ -319,9 +320,17 @@ class ChatViewModel(
|
||||
}
|
||||
|
||||
WsMethods.SESSION_RESUME -> {
|
||||
_uiState.update { it.copy(isLoading = false) }
|
||||
// Load cached messages from Room before the REST call
|
||||
val sessionId = _uiState.value.currentSessionId
|
||||
val resultMap = result as? Map<String, Any?>
|
||||
val sessionId =
|
||||
(resultMap?.get("session_id") as? String)
|
||||
?: _uiState.value.currentSessionId
|
||||
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
isLoading = false,
|
||||
currentSessionId = sessionId,
|
||||
)
|
||||
}
|
||||
if (sessionId != null) {
|
||||
loadCachedMessages(sessionId)
|
||||
}
|
||||
@@ -382,8 +391,11 @@ class ChatViewModel(
|
||||
}
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val requestId = wsClient.sendMessage(sessionId, text)
|
||||
pendingRequests[requestId] = WsMethods.PROMPT_SUBMIT
|
||||
wsClient.sendMessage(
|
||||
sessionId,
|
||||
text,
|
||||
onSent = { id -> pendingRequests[id] = WsMethods.PROMPT_SUBMIT },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -530,27 +542,30 @@ class ChatViewModel(
|
||||
fun interruptSession() {
|
||||
val sessionId = _uiState.value.currentSessionId ?: return
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val requestId =
|
||||
wsClient.send(
|
||||
WsMethods.SESSION_INTERRUPT,
|
||||
mapOf("session_id" to sessionId),
|
||||
)
|
||||
pendingRequests[requestId] = WsMethods.SESSION_INTERRUPT
|
||||
wsClient.send(
|
||||
WsMethods.SESSION_INTERRUPT,
|
||||
mapOf("session_id" to sessionId),
|
||||
onSent = { id -> pendingRequests[id] = WsMethods.SESSION_INTERRUPT },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun createNewSession() {
|
||||
_uiState.update { it.copy(isLoading = true, messages = emptyList()) }
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val requestId = wsClient.send(WsMethods.SESSION_CREATE)
|
||||
pendingRequests[requestId] = WsMethods.SESSION_CREATE
|
||||
wsClient.send(
|
||||
WsMethods.SESSION_CREATE,
|
||||
onSent = { id -> pendingRequests[id] = WsMethods.SESSION_CREATE },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun loadSessions() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val requestId = wsClient.send(WsMethods.SESSION_LIST)
|
||||
pendingRequests[requestId] = WsMethods.SESSION_LIST
|
||||
wsClient.send(
|
||||
WsMethods.SESSION_LIST,
|
||||
onSent = { id -> pendingRequests[id] = WsMethods.SESSION_LIST },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -562,18 +577,20 @@ class ChatViewModel(
|
||||
messages = emptyList(),
|
||||
currentSessionId = sessionId,
|
||||
showSessionPicker = false,
|
||||
isAgentTyping = false,
|
||||
isThinking = false,
|
||||
thinkingText = "",
|
||||
)
|
||||
}
|
||||
// Load cached messages first — instant display
|
||||
loadCachedMessages(sessionId)
|
||||
// Then resume session on server
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val requestId =
|
||||
wsClient.send(
|
||||
WsMethods.SESSION_RESUME,
|
||||
mapOf("session_id" to sessionId),
|
||||
)
|
||||
pendingRequests[requestId] = WsMethods.SESSION_RESUME
|
||||
wsClient.send(
|
||||
WsMethods.SESSION_RESUME,
|
||||
mapOf("session_id" to sessionId),
|
||||
onSent = { id -> pendingRequests[id] = WsMethods.SESSION_RESUME },
|
||||
)
|
||||
}
|
||||
loadSessionMessages(sessionId)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user