Fixed tool calling history
CI / Gateway plugin tests (push) Successful in 5m30s
CI / Kotlin tests (android host + desktop) (push) Successful in 6m57s

This commit is contained in:
ARIA committed 2026-08-22 13:22:07 +02:00
1 parent dd43033888
commit 524ed8ce53
11 files changed
+471 -55

No files matched your search

@@ -6,7 +6,9 @@ import iris.protocol.ChannelInfo
import iris.protocol.RuntimeMeta
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNull
import kotlin.test.assertTrue
/** Cache DB round-trip tests (JDBC in-memory SQLite; shared by both host targets). */
class ChatDbTest {
@@ -42,22 +44,80 @@ class ChatDbTest {
db.saveLanes(
mapOf(
"android:default" to
listOf(
listOf<ChatItem>(
msg("m1", ts = 100),
ToolItem(id = "tool_1", index = 0, name = "bash", anchorId = "m1"),
msg("m2", role = "assistant", text = "hi", ts = 200),
ToolItem(id = "tool_1", index = 0, name = "bash"),
),
"android:default::thr_1" to listOf(msg("m3", ts = 300)),
"android:default::thr_1" to listOf<ChatItem>(msg("m3", ts = 300)),
),
)
val loaded = db.loadLanes()
assertEquals(setOf("android:default", "android:default::thr_1"), loaded.keys)
// Tool cards are ephemeral — not persisted.
assertEquals(listOf("m1", "m2"), loaded["android:default"]!!.map { it.id })
// Tool cards are persisted and restored at their anchored position
// (after the message they follow, before the answer).
assertEquals(listOf("m1", "tool_1", "m2"), loaded["android:default"]!!.map { it.id })
assertEquals(listOf("m3"), loaded["android:default::thr_1"]!!.map { it.id })
// Ordered by ts.
assertEquals(100L, loaded["android:default"]!![0].ts)
assertEquals(200L, loaded["android:default"]!![1].ts)
// Messages ordered by ts.
assertEquals(100L, (loaded["android:default"]!![0] as MessageItem).ts)
assertEquals(200L, (loaded["android:default"]!![2] as MessageItem).ts)
}
@Test
fun toolCardsShareAnchorKeepOrder() {
val db = newDb()
db.saveLanes(
mapOf(
"android:default" to
listOf<ChatItem>(
msg("m1", ts = 100),
ToolItem(id = "tool_1", index = 0, name = "search_files", anchorId = "m1"),
ToolItem(id = "tool_2", index = 1, name = "terminal", anchorId = "m1"),
msg("m2", role = "assistant", ts = 200),
),
),
)
assertEquals(listOf("m1", "tool_1", "tool_2", "m2"), db.loadLanes()["android:default"]!!.map { it.id })
}
@Test
fun toolCardWithMissingAnchorFallsToEnd() {
val db = newDb()
db.saveLanes(
mapOf(
"android:default" to
listOf<ChatItem>(
msg("m1", ts = 100),
ToolItem(id = "tool_1", index = 0, name = "bash", anchorId = "deleted"),
msg("m2", role = "assistant", ts = 200),
),
),
)
assertEquals(listOf("m1", "m2", "tool_1"), db.loadLanes()["android:default"]!!.map { it.id })
}
@Test
fun restoreClosesOpenToolCard() {
val db = newDb()
db.saveLanes(
mapOf(
"android:default" to
listOf<ChatItem>(
msg("m1", ts = 100),
ToolItem(id = "tool_1", index = 0, name = "bash", done = false, anchorId = "m1"),
ToolItem(id = "tool_2", index = 1, name = "ls", done = true, ok = true, anchorId = "m1"),
),
),
)
val lane = db.loadLanes()["android:default"]!!
// The process died before tool.end — the open card is closed as
// interrupted, not left spinning.
val open = lane.first { it.id == "tool_1" } as ToolItem
assertTrue(open.done)
assertFalse(open.ok)
// A completed card is untouched.
val done = lane.first { it.id == "tool_2" } as ToolItem
assertTrue(done.ok)
}
@Test
@@ -82,21 +142,22 @@ class ChatDbTest {
),
)
val lane = db.loadLanes()["android:default"]!!
val msgItem = { id: String -> lane.first { it.id == id } as MessageItem }
// A pending send becomes failed (tap to retry); the gateway never
// acknowledged it before the process died.
assertEquals(MsgStatus.Failed, lane.first { it.id == "p1" }.status)
assertEquals(false, lane.first { it.id == "p1" }.pending)
assertEquals(MsgStatus.Failed, msgItem("p1").status)
assertEquals(false, msgItem("p1").pending)
// A streaming bubble is restored as finalized.
assertEquals(false, lane.first { it.id == "s1" }.streaming)
assertEquals(false, msgItem("s1").streaming)
// Read status is preserved.
assertEquals(MsgStatus.Read, lane.first { it.id == "r1" }.status)
assertEquals(MsgStatus.Read, msgItem("r1").status)
}
@Test
fun systemMessagesAreNotPersisted() {
val db = newDb()
db.saveLanes(mapOf("android:default" to listOf(msg("sys_1", role = "system", isSystem = true))))
assertEquals(emptyMap<String, List<MessageItem>>(), db.loadLanes())
db.saveLanes(mapOf("android:default" to listOf<ChatItem>(msg("sys_1", role = "system", isSystem = true))))
assertTrue(db.loadLanes().isEmpty())
}
@Test
@@ -121,8 +182,8 @@ class ChatDbTest {
),
),
)
db.saveLanes(mapOf("android:default" to listOf(item)))
val loaded = db.loadLanes()["android:default"]!!.first()
db.saveLanes(mapOf("android:default" to listOf<ChatItem>(item)))
val loaded = db.loadLanes()["android:default"]!!.first() as MessageItem
assertEquals("gpt", loaded.runtime?.model)
assertEquals("/tmp/a.png", loaded.media.first().localPath)
}
@@ -150,10 +211,10 @@ class ChatDbTest {
assertNull(db.metaGet("last_lane"))
db.metaPut("last_lane", "android:chan_1")
assertEquals("android:chan_1", db.metaGet("last_lane"))
db.saveLanes(mapOf("android:default" to listOf(msg("m1"))))
db.saveLanes(mapOf("android:default" to listOf<ChatItem>(msg("m1"))))
db.saveChannels(listOf(ChannelInfo(chatId = "android:default", name = "General")))
db.clearAll()
assertEquals(emptyMap<String, List<MessageItem>>(), db.loadLanes())
assertTrue(db.loadLanes().isEmpty())
assertEquals(emptyList<ChannelInfo>(), db.loadChannels())
assertNull(db.metaGet("last_lane"))
}