* feat(chat): live task delegation widget with inline card & sticky progress indicator (#736) * fix(chat): refine subagent isRunning status and fallback Compose list keys (#743) * feat(ci): add CodeQL analysis workflow & integrate todos into task widget (#736) - Add CodeQL workflow with manual build mode for java-kotlin - Render agent plan & todo items inside live delegation widget * ci(codeql): add advanced CodeQL workflow with continue-on-error for Kotlin 2.4.10 (#736) * ci(codeql): use build-mode none to prevent Kotlin 2.4.10 extractor error (#736) * revert(ci): remove codeql workflow (#736) * refactor(chat): remove inline task card and rely on sticky top bar (#736) - Keep StickySubagentBar and SubagentInspectionSheet for top widget UI --------- Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
1 parent
8ba92f2567
commit
2cf41ead06
8 files changed
+754
-31
No files matched your search
@@ -2,6 +2,7 @@ package com.m57.hermescontrol.ui.chat
|
||||
|
||||
import com.m57.hermescontrol.data.ws.WsEvent
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class ChatWsEventReducerTest {
|
||||
@@ -161,7 +162,7 @@ class ChatWsEventReducerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSubagentComplete_removesIndicator() {
|
||||
fun testSubagentComplete_updatesIndicatorToCompleted() {
|
||||
val initialIndicator =
|
||||
SubagentIndicator(
|
||||
type = "subagent.start",
|
||||
@@ -196,7 +197,12 @@ class ChatWsEventReducerTest {
|
||||
currentSessionId = "session-1",
|
||||
)
|
||||
|
||||
assertEquals(0, result.state.subagentIndicators.size)
|
||||
assertEquals(1, result.state.subagentIndicators.size)
|
||||
val indicator = result.state.subagentIndicators.first()
|
||||
assertEquals("subagent.complete", indicator.type)
|
||||
assertEquals("completed", indicator.status)
|
||||
assertEquals("done", indicator.summary)
|
||||
assertTrue(indicator.isComplete)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -233,4 +239,140 @@ class ChatWsEventReducerTest {
|
||||
assertEquals(ToolStatus.RUNNING, updatedMessage.toolStatus)
|
||||
assertEquals(null, updatedMessage.progressPreview)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSubagentEvent_accumulatesLiveTranscriptLogs() {
|
||||
val state =
|
||||
ChatUiState(
|
||||
currentSessionId = "session-1",
|
||||
subagentIndicators = emptyList(),
|
||||
)
|
||||
val startEvent =
|
||||
WsEvent.SubagentEvent(
|
||||
type = "subagent.start",
|
||||
sessionId = "session-1",
|
||||
payload =
|
||||
mapOf(
|
||||
"subagent_id" to "sub-1",
|
||||
"goal" to "research api",
|
||||
"text" to "Initializing subagent",
|
||||
),
|
||||
)
|
||||
|
||||
val res1 =
|
||||
ChatWsEventReducer.reduce(
|
||||
state = state,
|
||||
streamingState = StreamingState(),
|
||||
event = startEvent,
|
||||
currentSessionId = "session-1",
|
||||
)
|
||||
|
||||
val progressEvent =
|
||||
WsEvent.SubagentEvent(
|
||||
type = "subagent.progress",
|
||||
sessionId = "session-1",
|
||||
payload =
|
||||
mapOf(
|
||||
"subagent_id" to "sub-1",
|
||||
"text" to "Fetching documentation",
|
||||
),
|
||||
)
|
||||
|
||||
val res2 =
|
||||
ChatWsEventReducer.reduce(
|
||||
state = res1.state,
|
||||
streamingState = StreamingState(),
|
||||
event = progressEvent,
|
||||
currentSessionId = "session-1",
|
||||
)
|
||||
|
||||
val indicator = res2.state.subagentIndicators.first()
|
||||
assertEquals(2, indicator.logs.size)
|
||||
assertEquals("Initializing subagent", indicator.logs[0].text)
|
||||
assertEquals("Fetching documentation", indicator.logs[1].text)
|
||||
assertTrue(indicator.isRunning)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMessageStart_prunesCompletedSubagents() {
|
||||
val completedSubagent =
|
||||
SubagentIndicator(
|
||||
type = "subagent.complete",
|
||||
goal = "finished task",
|
||||
subagentId = "sub-1",
|
||||
status = "completed",
|
||||
)
|
||||
val state =
|
||||
ChatUiState(
|
||||
currentSessionId = "session-1",
|
||||
subagentIndicators = listOf(completedSubagent),
|
||||
)
|
||||
val startEvent = WsEvent.MessageStart(sessionId = "session-1")
|
||||
|
||||
val result =
|
||||
ChatWsEventReducer.reduce(
|
||||
state = state,
|
||||
streamingState = StreamingState(),
|
||||
event = startEvent,
|
||||
currentSessionId = "session-1",
|
||||
)
|
||||
|
||||
assertTrue(result.state.subagentIndicators.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testToolStart_extractsAgentTodos() {
|
||||
val state = ChatUiState(currentSessionId = "session-1")
|
||||
val todoEvent =
|
||||
WsEvent.ToolStart(
|
||||
name = "todo",
|
||||
sessionId = "session-1",
|
||||
data =
|
||||
mapOf(
|
||||
"todos" to
|
||||
listOf(
|
||||
mapOf("id" to "1", "content" to "Inspect repo", "status" to "completed"),
|
||||
mapOf("id" to "2", "content" to "Implement feature", "status" to "in_progress"),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
val result =
|
||||
ChatWsEventReducer.reduce(
|
||||
state = state,
|
||||
streamingState = StreamingState(),
|
||||
event = todoEvent,
|
||||
currentSessionId = "session-1",
|
||||
)
|
||||
|
||||
assertEquals(2, result.state.todos.size)
|
||||
assertEquals("Inspect repo", result.state.todos[0].content)
|
||||
assertTrue(result.state.todos[0].isCompleted)
|
||||
assertEquals("Implement feature", result.state.todos[1].content)
|
||||
assertTrue(result.state.todos[1].isInProgress)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHydrateTodosFromMessages_parsesStoredToolMessage() {
|
||||
val todoMessage =
|
||||
ChatMessage(
|
||||
role = MessageRole.TOOL,
|
||||
toolName = "todo",
|
||||
content = """{"todos":[{"id":"a","content":"Write tests","status":"completed"}]}""",
|
||||
)
|
||||
val state = ChatUiState(messages = listOf(todoMessage), currentSessionId = "session-1")
|
||||
val event = WsEvent.MessageToken(token = "hello", sessionId = "session-1")
|
||||
|
||||
val result =
|
||||
ChatWsEventReducer.reduce(
|
||||
state = state,
|
||||
streamingState = StreamingState(),
|
||||
event = event,
|
||||
currentSessionId = "session-1",
|
||||
)
|
||||
|
||||
assertEquals(1, result.state.todos.size)
|
||||
assertEquals("Write tests", result.state.todos[0].content)
|
||||
assertTrue(result.state.todos[0].isCompleted)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user