feat: paginate, sync, and display branched sessions (#563)
* mobile: 以 Desktop Sessions 作为聊天历史源 * mobile: 修复快速连接崩溃与无标题会话隐藏 * mobile: 对齐 Desktop 标题并恢复完整历史 * mobile: 分离 Desktop 存储 ID 与运行 Session ID * mobile: 将新对话持久化为 Desktop Session * mobile: 渲染 Desktop 本地媒体 * feat: 分页加载并同步当前会话 * fix: 显示分支会话名称与层级 * fix: align PR with upstream app contract * fix: preserve upstream session RPC lifecycle
This commit is contained in:
1 parent
306861a222
commit
3052db7d65
8 files changed
+405
-79
No files matched your search
@@ -0,0 +1,45 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class SessionTreeTest {
|
||||
@Test
|
||||
fun nestsBranchesAndUsesDesktopFallbackNames() {
|
||||
val sessions =
|
||||
listOf(
|
||||
SessionInfo(id = "unrelated", title = "Recent root"),
|
||||
SessionInfo(id = "branch-b", parent_session_id = "parent"),
|
||||
SessionInfo(id = "parent", title = "Original"),
|
||||
SessionInfo(id = "branch-a", parent_session_id = "parent", preview = "copied prompt"),
|
||||
SessionInfo(id = "nested", parent_session_id = "branch-a", title = "Named nested branch"),
|
||||
)
|
||||
|
||||
val tree = flattenSessionTree(sessions)
|
||||
|
||||
assertEquals(listOf("unrelated", "parent", "branch-b", "branch-a", "nested"), tree.map { it.session.id })
|
||||
assertEquals(listOf(0, 0, 1, 1, 2), tree.map { it.depth })
|
||||
assertEquals(
|
||||
listOf("Recent root", "Original", "branch 1", "branch 2", "Named nested branch"),
|
||||
tree.map {
|
||||
it.displayTitle
|
||||
},
|
||||
)
|
||||
assertEquals(listOf(null, null, "├─", "└─", "└─"), tree.map { it.branchStem })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun keepsMissingParentsAndCyclesVisible() {
|
||||
val sessions =
|
||||
listOf(
|
||||
SessionInfo(id = "orphan", parent_session_id = "missing", preview = "orphan preview"),
|
||||
SessionInfo(id = "a", parent_session_id = "b"),
|
||||
SessionInfo(id = "b", parent_session_id = "a"),
|
||||
)
|
||||
|
||||
val tree = flattenSessionTree(sessions)
|
||||
|
||||
assertEquals(setOf("orphan", "a", "b"), tree.map { it.session.id }.toSet())
|
||||
assertEquals("orphan preview", tree.first { it.session.id == "orphan" }.displayTitle)
|
||||
}
|
||||
}
|
||||
+21
-1
@@ -141,7 +141,8 @@ class HermesApiServiceMockWebServerTest {
|
||||
"created_at": "2026-06-20T14:30:00Z",
|
||||
"message_count": 7,
|
||||
"status": "idle",
|
||||
"source": "web"
|
||||
"source": "web",
|
||||
"parent_session_id": "abc-123"
|
||||
}
|
||||
],
|
||||
"total": 2,
|
||||
@@ -173,6 +174,7 @@ class HermesApiServiceMockWebServerTest {
|
||||
assertEquals(7, second.message_count)
|
||||
assertEquals("idle", second.status)
|
||||
assertEquals("web", second.source)
|
||||
assertEquals("abc-123", second.parent_session_id)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -254,6 +256,24 @@ class HermesApiServiceMockWebServerTest {
|
||||
assertEquals("tool_execution", messages[2].type)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getSessionMessages_sendsPaginationQuery() =
|
||||
runBlocking {
|
||||
mockServer.enqueue(
|
||||
MockResponse()
|
||||
.setResponseCode(200)
|
||||
.setBody("{\"messages\": []}"),
|
||||
)
|
||||
|
||||
val response = api.getSessionMessages("desktop/session", limit = 150, offset = 300)
|
||||
|
||||
assertTrue(response.isSuccessful)
|
||||
assertEquals(
|
||||
"/api/sessions/desktop/session/messages?limit=150&offset=300",
|
||||
mockServer.takeRequest().path,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getSessionMessages_withNullRole_doesNotCrash() =
|
||||
runBlocking {
|
||||
|
||||
Reference in new issue
Block a user