fix(jetbrains): prune deleted sessions in the merged activity snapshot

activitySnapshot() merged statuses and activity, two StateFlows that each
prune the removed-session set through their own collector. After a delete,
one flow can still carry the session while the other has already dropped
it, so the snapshot briefly reported a deleted session as RUNNING and any
consumer reading it at that moment rendered a stale badge.

Subtract the removed set in the snapshot itself so the merged view is
consistent regardless of collector ordering, and wait on both flows in the
test that asserts every activity kind, which had the same latent race.

This fixes the flaky KiloSessionServiceTest failure on main.
This commit is contained in:
kirillk
2026-08-26 09:28:41 -04:00
parent 82ec63727a
commit 17bef75509
3 changed files with 13 additions and 2 deletions
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": patch
---
Stop showing a running badge for a session that was just deleted.
@@ -124,10 +124,14 @@ class KiloSessionService internal constructor(
* Per-session activity for history and session lists. [activity] is the richer source — it also
* carries waiting and failed sessions, and it covers sessions that are not open — but it drops
* sessions whose directory the backend cannot resolve, so the busy statuses stay as a fallback.
*
* [statuses] and [activity] prune [removed] through separate collectors, so one can still carry
* a deleted session while the other has already dropped it. Subtracting [removed] here keeps the
* merged snapshot consistent instead of briefly badging a deleted session as running.
*/
internal fun activitySnapshot(): Map<String, SessionActivityKind> {
val busy = statuses.value.filterValues { it.type == "busy" }.mapValues { SessionActivityKind.RUNNING }
return busy + activity.value.mapValues { it.value.kind.toKind() }
return (busy + activity.value.mapValues { it.value.kind.toKind() }) - removed.value
}
suspend fun list(dir: String): SessionListDto {
@@ -151,7 +151,9 @@ class KiloSessionServiceTest : BasePlatformTestCase() {
"ses_failed" to SessionActivityDto("/repo/wt", SessionActivityKindDto.ERROR),
"ses_asking" to SessionActivityDto("/repo/wt", SessionActivityKindDto.QUESTION),
)
service.activity.first { it.isNotEmpty() }
// Both maps feed the snapshot through separate collectors, so wait for each one.
service.statuses.first { it.isNotEmpty() }
service.activity.first { it.size == 2 }
assertEquals(
mapOf(