Show live background task counts

This commit is contained in:
iris-ai committed 2026-09-15 13:44:32 -04:00
1 parent 9fd21af4e8
commit 8262ceb786
16 files changed
+160 -29

No files matched your search

@@ -225,6 +225,8 @@ data class SessionSummary(
val usageProvider: String?,
val status: String,
val lastActivity: Double,
/** Latest measured number of live background tasks; zero also covers older servers. */
val backgroundTasks: Int,
/**
* How many subagents this session has, however their own status now reads.
*
@@ -263,6 +265,7 @@ private fun parseSession(session: JSONObject) =
usageProvider = session.optString("usageProvider").ifEmpty { null },
status = session.getString("status"),
lastActivity = session.getDouble("lastActivity"),
backgroundTasks = session.optInt("backgroundTasks", 0),
subagents = session.optInt("subagents", 0),
)
@@ -140,6 +140,9 @@ sealed class SessionEvent {
/** The same command, handed to the session. */
data class CommandSent(val id: String, val text: String) : SessionEvent()
/** Provider-reported number of background tasks alive now. */
data class BackgroundTasks(val count: Int) : SessionEvent()
data class Status(val state: String) : SessionEvent()
/**
@@ -283,6 +286,7 @@ fun parseSeqEvent(json: String): SeqEvent {
"commandQueued" ->
SessionEvent.CommandQueued(body.getString("id"), body.getString("text"))
"commandSent" -> SessionEvent.CommandSent(body.getString("id"), body.getString("text"))
"backgroundTasks" -> SessionEvent.BackgroundTasks(body.getInt("count"))
"status" -> SessionEvent.Status(body.getString("state"))
"settings" ->
SessionEvent.Settings(
@@ -554,6 +554,14 @@ private fun SessionCard(
modifier = Modifier.weight(1f),
)
StatusText(session.status)
if (session.backgroundTasks > 0) {
Text(
backgroundTaskLabel(session.backgroundTasks),
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(start = 8.dp),
)
}
}
Spacer(Modifier.height(4.dp))
Row(modifier = Modifier.fillMaxWidth()) {
@@ -256,6 +256,8 @@ fun SessionScreen(
val topEdgeHeld = remember { TopEdgeHold() }
var items by remember { mutableStateOf(listOf<TranscriptItem>()) }
var status by remember { mutableStateOf(subagent?.status ?: summary.status) }
var backgroundTasks by
remember(address) { mutableIntStateOf(if (isSubagent) 0 else summary.backgroundTasks) }
// Seeded from the row this screen was opened from, so a conversation already under way says how
// much it is holding before any turn happens here. Null is "nobody has measured it", which is a
// different answer from an empty context and is drawn differently.
@@ -538,6 +540,9 @@ fun SessionScreen(
}
status = event.state
}
if (event is SessionEvent.BackgroundTasks && !isSubagent) {
backgroundTasks = event.count
}
if (!isSubagent) loginOpen = authenticationPromptAfter(loginOpen, event)
// In order, always: one late event recorded ahead of the backlog would fold a
// streamed delta into whatever row happened to be last by then.
@@ -1842,6 +1847,7 @@ fun SessionScreen(
status = status,
compactingFor = compactingFor,
contextTokens = contextTokens,
backgroundTasks = backgroundTasks,
subagent = isSubagent,
)
@@ -2362,6 +2368,8 @@ private fun SessionStatusRow(
compactingFor: Long?,
/** Context the session is holding, or null where nothing has measured it. */
contextTokens: Long?,
/** Provider-reported live background work; zero is deliberately not drawn. */
backgroundTasks: Int,
modifier: Modifier = Modifier,
/**
* Whether this row is for a subagent rather than a session, which changes only one word:
@@ -2395,6 +2403,14 @@ private fun SessionStatusRow(
// its own contrast, since the surface under it will not change to rescue it.
color = commandColor,
)
if (backgroundTasks > 0) {
Text(
backgroundTaskLabel(backgroundTasks),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(start = 8.dp),
)
}
LinearProgressIndicator(
color = commandColor,
trackColor = MaterialTheme.colorScheme.surfaceContainerHigh,
@@ -2415,6 +2431,14 @@ private fun SessionStatusRow(
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(start = 8.dp),
)
if (backgroundTasks > 0) {
Text(
backgroundTaskLabel(backgroundTasks),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(start = 8.dp),
)
}
Spacer(Modifier.weight(1f))
}
// Every remaining state says which one it is, including the quiet one. The row used to
@@ -2423,13 +2447,22 @@ private fun SessionStatusRow(
// showed nothing at all. The words and the colour are `sessionStatusWord`'s and
// `sessionStatusColour`'s, shared with the session list so one state is not called two
// things -- or drawn two colours -- depending which screen you are on.
else ->
else -> {
Text(
sessionStatusWord(status, subagent),
style = MaterialTheme.typography.labelSmall,
color = sessionStatusColour(status),
modifier = Modifier.weight(1f),
)
if (backgroundTasks > 0) {
Text(
backgroundTaskLabel(backgroundTasks),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(start = 8.dp),
)
}
Spacer(Modifier.weight(1f))
}
}
// How full the session is, which is the number a reader is asking about -- how much room is
// left before the next compaction -- rather than what has been spent getting here.
@@ -38,6 +38,8 @@ fun sessionStatusWord(status: String, subagent: Boolean = false): String =
else -> status
}
fun backgroundTaskLabel(count: Int): String = "$count bg ${if (count == 1) "task" else "tasks"}"
/**
* The colour that goes with [sessionStatusWord]: the accent is spent on the states that are about
* to do something or want something, and every quiet one shares the muted colour.
@@ -517,6 +517,7 @@ fun foldEvent(items: List<TranscriptItem>, entry: SeqEvent): List<TranscriptItem
// nothing it belongs above.
is SessionEvent.MessageDropped -> items
is SessionEvent.Settings -> items
is SessionEvent.BackgroundTasks -> items
is SessionEvent.Status -> settleReply(items, event.state)
is SessionEvent.AuthenticationRequired ->
items + TranscriptItem.ErrorMsg(entry.seq, event.message)