feat(chat): support multi-select, batch multi-question clarify prompts, and expiry (fixes #1045) (#1055)

This commit is contained in:
M57 authored and GitHub committed 2026-09-03 21:38:57 +03:00
1 parent 50773945f9
commit 6d2362ba8a
10 files changed
+649 -109

No files matched your search

@@ -327,6 +327,79 @@ class EventParserTest {
assertEquals("q0", clarifyEvent.questionId)
}
@Test
fun testParseClarifyRequest_withMultiSelectAndMultipleQuestions() {
val response =
createJsonRpcResponse(
jsonrpc = "2.0",
id = null,
result = null,
error = null,
method = "event",
params =
mapOf(
"type" to "clarify.request",
"payload" to
mapOf(
"request_id" to "req-batch-2",
"questions" to
listOf(
mapOf(
"qid" to "q0",
"question" to "Pick languages:",
"choices" to listOf("Python", "Go", "Rust"),
"multi_select" to true,
),
mapOf(
"qid" to "q1",
"question" to "Any comments?",
"choices" to emptyList<String>(),
"multi_select" to false,
),
),
),
),
)
val event = EventParser.parse(response)
assertTrue(event is WsEvent.ClarifyRequest)
val clarifyEvent = event as WsEvent.ClarifyRequest
assertEquals("req-batch-2", clarifyEvent.clarifyId)
assertEquals(2, clarifyEvent.questions.size)
val q0 = clarifyEvent.questions[0]
assertEquals("q0", q0.qid)
assertEquals("Pick languages:", q0.question)
assertEquals(listOf("Python", "Go", "Rust"), q0.choices)
assertTrue(q0.multiSelect)
val q1 = clarifyEvent.questions[1]
assertEquals("q1", q1.qid)
assertEquals("Any comments?", q1.question)
assertTrue(q1.choices.isEmpty())
assertFalse(q1.multiSelect)
}
@Test
fun testParseClarifyExpire_parsesSuccessfully() {
val response =
createJsonRpcResponse(
jsonrpc = "2.0",
id = null,
result = null,
error = null,
method = "event",
params =
mapOf(
"type" to "clarify.expire",
"payload" to mapOf("request_id" to "req-expire-1"),
),
)
val event = EventParser.parse(response)
assertTrue(event is WsEvent.ClarifyExpire)
val expireEvent = event as WsEvent.ClarifyExpire
assertEquals("req-expire-1", expireEvent.clarifyId)
}
// ── TEST-07: Untested subtypes ─────────────────────────────────────
@Test
@@ -1407,6 +1407,82 @@ class ChatViewModelTest {
}
}
@Test
fun testClarifyBatch_respondsToAllQuestions() =
runTest {
val (viewModel, sessionId) = createViewModelWithSession()
mockEventsFlow.emit(
WsEvent.ClarifyRequest(
text = "1. Pick A\n\n2. Pick B",
options = listOf("A1", "A2"),
clarifyId = "clarify-batch-multi",
sessionId = sessionId,
questionId = "q0",
questions =
listOf(
WsEvent.ClarifyQuestion(
qid = "q0",
question = "Pick A",
choices = listOf("A1", "A2"),
multiSelect = false,
),
WsEvent.ClarifyQuestion(
qid = "q1",
question = "Pick B",
choices = listOf("B1", "B2"),
multiSelect = true,
),
),
),
)
advanceUntilIdle()
val clarify = viewModel.uiState.value.clarifyRequest
assertNotNull(clarify)
assertEquals(2, clarify?.resolvedQuestions?.size)
viewModel.respondToClarifyBatch(
mapOf(
"q0" to "A1",
"q1" to "B1, B2",
),
)
advanceUntilIdle()
assertNull(viewModel.uiState.value.clarifyRequest)
verify {
HermesWsClient.send(
method = WsMethods.CLARIFY_RESPOND,
params =
mapOf(
"session_id" to sessionId,
"response" to "A1",
"answer" to "A1",
"question_id" to "q0",
"clarify_id" to "clarify-batch-multi",
"request_id" to "clarify-batch-multi",
),
onSent = any(),
)
}
verify {
HermesWsClient.send(
method = WsMethods.CLARIFY_RESPOND,
params =
mapOf(
"session_id" to sessionId,
"response" to "B1, B2",
"answer" to "B1, B2",
"question_id" to "q1",
"clarify_id" to "clarify-batch-multi",
"request_id" to "clarify-batch-multi",
),
onSent = any(),
)
}
}
@Test
fun testClarifyRequestCustomResponse() =
runTest {
@@ -26,6 +26,25 @@ class ChatWsEventReducerTest {
assertEquals(null, result.state.clarifyRequest)
}
@Test
fun testClarifyExpire_clearsMatchingClarifyRequest() {
val state =
ChatUiState(
currentSessionId = "session-1",
clarifyRequest = ClarifyUi("Expiring question", emptyList(), "clarify-expire-1"),
)
val result =
ChatWsEventReducer.reduce(
state = state,
streamingState = StreamingState(),
event = WsEvent.ClarifyExpire(clarifyId = "clarify-expire-1", sessionId = "session-1"),
currentSessionId = "session-1",
)
assertEquals(null, result.state.clarifyRequest)
}
@Test
fun testToolProgress_updatesProgressPreviewForMatchingRunningTool() {
val initialMessage =