Order the session list by when each agent was turned on

A running session no longer moves: the ones with a process come first,
oldest start first, so starting one appends it to the bottom of that
group and nothing it goes on to do -- beginning a turn, finishing one,
asking a question -- can shift it. Sorting by activity with the
awaiting-answer ones floated to the top is what this replaces; the
status word and its colour already say which session wants something
without the row having to move to say it. Stopped sessions are a group
below, most recently active first.

The order is the server's: `SessionConfig::started` is written each time
a process is started for a session and reported as `started`, so it is
the same on every device and survives a backend restart -- which adopts
processes rather than starting them, and so could not work the times out
for itself. Applied on the phone, because presentation order is a
display decision.

`LiveSession::info` takes the session's config entry rather than a
parameter per field read from it, which is what `AutoResumeView` existed
to bundle; that goes.

Verified on the emulator against the sandbox: three echo sessions kept
their order while the newest-active one was messaged; a stopped and
restarted session moved below one started after it; a stopped session
dropped below every running one; and after a backend restart the
recorded times came back unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
iris-aiandClaude Opus 5 committed 2026-09-15 23:54:36 -04:00
1 parent a9cfea89e5
commit cbae7ee8c0
6 files changed
+255 -64

No files matched your search

@@ -0,0 +1,86 @@
package com.example.aiapp
import kotlin.test.Test
import kotlin.test.assertEquals
class SessionOrderTest {
@Test
fun `running sessions keep the order their agents were turned on in`() {
val first = session("first", status = "running", started = 10.0, lastActivity = 900.0)
val second =
session("second", status = "awaitingInput", started = 20.0, lastActivity = 20.0)
val third = session("third", status = "waiting", started = 30.0, lastActivity = 500.0)
assertEquals(
listOf("first", "second", "third"),
sessionsInListOrder(listOf(third, second, first)).map { it.id },
)
}
@Test
fun `a session started again joins the bottom of the running group`() {
val old = session("old", status = "idle", started = 10.0, lastActivity = 10.0)
val restarted = session("restarted", status = "idle", started = 99.0, lastActivity = 99.0)
assertEquals(
listOf("old", "restarted"),
sessionsInListOrder(listOf(restarted, old)).map { it.id },
)
}
@Test
fun `stopped sessions come after the running ones, most recent first`() {
val running = session("running", status = "idle", started = 100.0, lastActivity = 100.0)
val stale = session("stale", status = "exited", started = 1.0, lastActivity = 5.0)
val recent = session("recent", status = "exited", started = 2.0, lastActivity = 50.0)
assertEquals(
listOf("running", "recent", "stale"),
sessionsInListOrder(listOf(stale, recent, running)).map { it.id },
)
}
/**
* A session whose process nobody could ask about still has one, so it stays where it was rather
* than dropping into the stopped group the moment a machine goes quiet.
*/
@Test
fun `a session of unknown state is one of the running ones`() {
val unknown = session("unknown", status = "unknown", started = 10.0, lastActivity = 10.0)
val stopped = session("stopped", status = "exited", started = 5.0, lastActivity = 999.0)
assertEquals(
listOf("unknown", "stopped"),
sessionsInListOrder(listOf(stopped, unknown)).map { it.id },
)
}
private fun session(id: String, status: String, started: Double, lastActivity: Double) =
SessionSummary(
id = id,
machine = "machine",
machineName = "machine",
provider = "echo",
title = id,
model = null,
keepsOwnTranscript = false,
ownTranscriptName = null,
permissionMode = null,
effort = null,
takesEffort = false,
imported = false,
notify = true,
autoResume = false,
autoResumeMessage = DEFAULT_RESUME_MESSAGE,
resumeAt = null,
cwd = null,
contextTokens = null,
maxImageEdge = null,
usageProvider = null,
status = status,
lastActivity = lastActivity,
started = started,
backgroundTasks = 0,
subagents = 0,
)
}