fix(chat): restore pending clarify bubble from resume/info replay (#1264)
Co-authored-by: m57 <41645758+Hy4ri@users.noreply.github.com>
This commit is contained in:
1 parent
2938f607f0
commit
719a89b951
1 file changed
+70
@@ -30,6 +30,7 @@ import com.m57.hermescontrol.data.ws.CommandBlocklist
|
||||
import com.m57.hermescontrol.data.ws.CommandCatalog
|
||||
import com.m57.hermescontrol.data.ws.ConnectionOperationParser
|
||||
import com.m57.hermescontrol.data.ws.ConnectionStatus
|
||||
import com.m57.hermescontrol.data.ws.EventParser
|
||||
import com.m57.hermescontrol.data.ws.HermesWsClient
|
||||
import com.m57.hermescontrol.data.ws.JsonRpcError
|
||||
import com.m57.hermescontrol.data.ws.WsEvent
|
||||
@@ -1532,9 +1533,67 @@ class ChatViewModel(
|
||||
runtimeSessionId ?: _uiState.value.currentSessionId,
|
||||
)
|
||||
}
|
||||
// Session.info can carry "pending_clarify" the same way — restore
|
||||
// the clarify bubble so a question asked while detached stays
|
||||
// answerable after leaving and re-entering the chat.
|
||||
val pendingClarify = info["pending_clarify"] as? Map<*, *>
|
||||
if (pendingClarify != null) {
|
||||
surfacePendingClarify(
|
||||
pendingClarify,
|
||||
runtimeSessionId ?: _uiState.value.currentSessionId,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore a pending clarify from the "pending_clarify" replay payload that
|
||||
* "session.resume" / "session.info" carry while a clarify blocks the turn
|
||||
* server-side (gateway _live_session_payload). Without this, leaving and
|
||||
* re-entering a chat shows the "waiting for input" status with no way to
|
||||
* answer: the live "clarify.request" event only reached whatever client
|
||||
* was attached when the agent asked.
|
||||
*
|
||||
* The payload has the same shape as a live "clarify.request" event (batch
|
||||
* "questions" or legacy "question"/"choices", plus the "request_id" the
|
||||
* answer must reference), so reuse [EventParser] and feed the typed event
|
||||
* through [handleWsEvent] — the exact path a live question takes. Batch
|
||||
* replays may also carry locked per-question "answers".
|
||||
*/
|
||||
private fun surfacePendingClarify(
|
||||
payload: Map<*, *>,
|
||||
sessionId: String?,
|
||||
) {
|
||||
val clarifyId = (payload["clarify_id"] ?: payload["request_id"]) as? String
|
||||
if (clarifyId != null && _uiState.value.clarifyRequest?.clarifyId == clarifyId) {
|
||||
// Already on screen — avoid clobbering in-progress answer state.
|
||||
return
|
||||
}
|
||||
val clarifyPayload: Map<String, Any?> = payload.entries.associate { (k, v) -> k.toString() to v }
|
||||
val event =
|
||||
EventParser.parseParams(
|
||||
mapOf(
|
||||
"type" to "clarify.request",
|
||||
"session_id" to sessionId,
|
||||
"payload" to clarifyPayload,
|
||||
),
|
||||
) as? WsEvent.ClarifyRequest ?: return
|
||||
if (event.questions.isEmpty() && event.text.isNullOrBlank() && event.options.isNullOrEmpty()) {
|
||||
return
|
||||
}
|
||||
// "answers" is replay-only (locked batch answers); mirror it into the
|
||||
// event so restored questions render their answered state.
|
||||
val lockedAnswers =
|
||||
(clarifyPayload["answers"] as? Map<*, *>)
|
||||
?.mapNotNull { (qid, answer) ->
|
||||
val id = qid as? String ?: return@mapNotNull null
|
||||
val text = answer as? String ?: return@mapNotNull null
|
||||
id to text
|
||||
}?.toMap()
|
||||
?: emptyMap()
|
||||
handleWsEvent(event.copy(lockedAnswers = lockedAnswers))
|
||||
}
|
||||
|
||||
// ── RPC response handling ────────────────────────────────────────────
|
||||
|
||||
private fun settleOutgoingResult(
|
||||
@@ -1821,6 +1880,17 @@ class ChatViewModel(
|
||||
runtimeSessionId ?: sessionId,
|
||||
)
|
||||
}
|
||||
// Reconnect replay: resume payload can carry "pending_clarify"
|
||||
// (server _live_session_payload) — restore the clarify bubble
|
||||
// so questions asked while the client was detached remain
|
||||
// answerable after leaving and re-entering the session.
|
||||
val pendingClarify = resultMap["pending_clarify"] as? Map<*, *>
|
||||
if (pendingClarify != null) {
|
||||
surfacePendingClarify(
|
||||
pendingClarify,
|
||||
runtimeSessionId ?: sessionId,
|
||||
)
|
||||
}
|
||||
val activeSessionId = runtimeSessionId ?: sessionId
|
||||
// Reconnect replay: the backend-owned connector operation is
|
||||
// authoritative and uses the same full snapshot as the live
|
||||
|
||||
Reference in new issue
Block a user