Keep the running tool call outside its group

A run of adjacent calls is drawn as one collapsed card, which hid the one
thing worth seeing without opening anything: the command the session is
running right now. It is a row of its own while it runs and folds back into
the run when it ends.

Grouping stays a display decision, so the pieces a running call cuts a run
into are keyed there. The first piece keeps the run's name -- that name is
what survives a page of history landing in front of it -- and later pieces
take their own first call's id behind it, since the call a run was named
after can itself be the one running.

The echo rig's /tools gap now runs between a call's start and its end rather
than between one call and the next, which is where a real session's time goes
and what makes the running state observable at all.

Checked with ktfmtFormat, compileDebugKotlin, testDebugUnitTest (new
ToolRowsTest) and lintDebug, cargo fmt/clippy/test, and on the emulator
against the sandbox: "Called 2 tools" with the live Bash card beneath it.
This commit is contained in:
iris-ai committed 2026-09-15 01:13:03 -04:00
1 parent b9b777acaf
commit 036eb375aa
5 files changed
+172 -45

No files matched your search

@@ -302,10 +302,10 @@ fun SessionScreen(
// words were in. This is whatever was true as of the last frame.
val selecting = selection.selectedTexts.isNotEmpty()
var expandedTools by remember { mutableStateOf(setOf<String>()) }
// Which runs of adjacent tool calls are open. Keyed by the first call's id, so a group survives
// Which runs of adjacent tool calls are open, by the group row's own key, so a group survives
// more calls arriving after it.
var expandedGroups by remember { mutableStateOf(setOf<String>()) }
// Runs already drawn as a group, so the transition into one is noticed exactly once.
// Calls already drawn inside a group, so being folded into one is noticed exactly once each.
var everGrouped by remember { mutableStateOf(setOf<String>()) }
// Which messages from other agents are open, by the seq that identifies their row. Closed by
// default, which is the rule for anything new in this transcript.
@@ -692,19 +692,23 @@ fun SessionScreen(
}
}
// A call opened on its own stays open when a second call in the same run turns it into a group.
// Until this, watching a Bash call and having the session make another one shut the one being
// read and folded it behind "Called 2 tools".
// A call opened on its own stays open when it is folded into a group -- either because a second
// call in the same run arrived, or because it finished and rejoined the run it was running
// outside of. Until this, watching a Bash call and having the session make another one shut the
// one being read and folded it behind "Called 2 tools".
//
// Considered once per run, at the moment it first becomes a group, and never again: after that
// the group's own toggle owns it.
// Per call rather than per group, because a group outlives the calls joining it: considered at
// the moment each call first lands inside one, and never again, so the reader who then shuts
// the group has shut it.
LaunchedEffect(rows) {
val fresh = rows.filterIsInstance<TranscriptRow.Tools>().filter { it.id !in everGrouped }
val fresh =
rows.filterIsInstance<TranscriptRow.Tools>().flatMap { group ->
group.calls.filter { it.id !in everGrouped }.map { group.key to it.id }
}
if (fresh.isEmpty()) return@LaunchedEffect
expandedGroups =
expandedGroups +
fresh.filter { group -> group.calls.any { it.id in expandedTools } }.map { it.id }
everGrouped = everGrouped + fresh.map { it.id }
expandedGroups + fresh.filter { it.second in expandedTools }.map { it.first }
everGrouped = everGrouped + fresh.map { it.second }
}
// A compaction reports nothing about its own progress -- measured against the CLI, which says
@@ -1624,13 +1628,13 @@ fun SessionScreen(
is TranscriptRow.Tools ->
ToolGroup(
group = row,
expanded = row.id in expandedGroups,
expanded = row.key in expandedGroups,
onToggle = {
toggleAnchored(row) {
expandedGroups =
if (row.id in expandedGroups)
expandedGroups - row.id
else expandedGroups + row.id
if (row.key in expandedGroups)
expandedGroups - row.key
else expandedGroups + row.key
}
},
isToolExpanded = { it in expandedTools },