feat: adopt side-questions via prompt.btw and /btw command in Chat (fixes #1015) (#1026)

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
M57andM57 authored and GitHub committed 2026-08-31 15:54:09 +03:00
1 parent b5c4ba50cd
commit 53c8eca714
12 files changed
+487 -2

No files matched your search

@@ -478,6 +478,40 @@ class ChatViewModelTest {
verify { HermesWsClient.send(WsMethods.SESSION_INTERRUPT, any(), any()) }
}
@Test
fun testSlashCommand_btw_dispatchesPromptBtw_andSetsBtwState() =
runTest {
val (viewModel, sessionId) = createViewModelWithSession()
every {
HermesWsClient.request(WsMethods.PROMPT_BTW, any(), any())
} returns
CompletableDeferred(
mapOf("task_id" to "btw_123456"),
)
viewModel.sendMessage("/btw which file was that in?")
advanceUntilIdle()
val btw = viewModel.uiState.value.btwState
assertNotNull(btw)
assertEquals("which file was that in?", btw?.question)
assertEquals("btw_123456", btw?.taskId)
assertTrue(btw?.isLoading == true)
verify {
HermesWsClient.request(
WsMethods.PROMPT_BTW,
mapOf("session_id" to sessionId, "text" to "which file was that in?"),
any(),
)
}
// Ensure transcript messages were NOT polluted with the side question
assertTrue(
viewModel.uiState.value.messages
.none { it.content.contains("which file was that in?") },
)
}
// ── Slash usage ranking (issue #865) ────────────────────────────────────
@Test
@@ -535,6 +535,43 @@ class ChatWsEventReducerTest {
assertEquals("💾 Self-improvement review: Skill 'android-ci' patched", msg.content)
}
@Test
fun testBtwComplete_updatesBtwState() {
val state =
ChatUiState(
currentSessionId = "session-1",
btwState =
BtwUiState(
question = "what model?",
isLoading = true,
),
)
val event =
WsEvent.BtwComplete(
taskId = "btw_123456",
question = "what model?",
text = "This conversation uses Gemini 3.7.",
sessionId = "session-1",
)
val result =
ChatWsEventReducer.reduce(
state = state,
streamingState = StreamingState(),
event = event,
currentSessionId = "session-1",
)
val btw = result.state.btwState
org.junit.Assert.assertNotNull(btw)
assertEquals("btw_123456", btw?.taskId)
assertEquals("what model?", btw?.question)
assertEquals("This conversation uses Gemini 3.7.", btw?.answer)
assertEquals(false, btw?.isLoading)
// Zero messages appended to transcript!
assertEquals(0, result.state.messages.size)
}
// ── Issue #771: reasoning survives a tool call (regression) ────────────
//
// Real gateway capture for `run echo hi` (reasoning model):
@@ -74,6 +74,14 @@ class SlashCommandDispatcherTest {
assertEquals(SlashResult.OpenHistory, dispatcher.dispatch("/History"))
}
@Test
fun `btw routes to SideQuestion`() {
assertEquals(SlashResult.SideQuestion("what model is this?"), dispatcher.dispatch("/btw what model is this?"))
assertEquals(SlashResult.SideQuestion("what model is this?"), dispatcher.dispatch("/BTW what model is this?"))
assertEquals(SlashResult.SideQuestion(""), dispatcher.dispatch("/btw"))
assertEquals(SlashResult.SideQuestion(""), dispatcher.dispatch("/btw "))
}
@Test
fun `NEW uppercase still routes to NewSession`() {
// Dispatcher lower-cases before matching, so case must not matter.