Order a session's subagents by activity, and delete finished ones by holding

The subcards were oldest first, which buried whatever is working now. They
are ordered on the phone -- still running first, then most recently active --
over the server's stable oldest-first answer, since presentation order is a
display decision and a subagent that is thinking reports nothing meanwhile.

Holding a subcard selects it and several at a time, the import list's gesture
and its confirmation, so selecting is learned once. The selection bar sits
inside the session's card rather than at the bottom of the screen: it belongs
to one card, and one Delete is one request against one parent, so picking a
row in another card moves the selection rather than adding to it. Delete is
disabled, with the reason in words, while anything selected is still running
-- its transcript is still being written to and its process is the session's
to stop, so the server refuses that batch outright.

`POST /sessions/{id}/subagents/delete` takes the batch and checks every id
before removing any, so a set naming a running one is left exactly as it was
rather than half-deleted. It is `Subagents::start`'s path out. What counts as
running is shared with the list route through `has_a_process`, so the two
cannot disagree. On success the phone takes those rows out of that one card
and off the session's count, purges its cached copies, and drops the
expansion when nothing is left -- nothing else is refetched.

Driven on the emulator against the sandbox with ui-trace's new hold-by-name:
selecting two, the dialog, the rows going, a running one holding Delete
disabled, and the expander leaving with the last subagent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
irisandClaude Opus 5 committed 2026-09-06 13:10:21 -04:00
1 parent cf10b17c5b
commit 13d2d11c2d
5 files changed
+506 -30

No files matched your search

@@ -305,6 +305,26 @@ fun fetchSubagents(settings: ServerSettings, sessionId: String): List<SubagentSu
}
}
/**
* Removes finished subagents from their session, transcripts and all.
*
* All or nothing, and only for subagents that have finished: the server checks every id before it
* removes any, so a batch naming one that is still running leaves the whole selection as it was.
* One request for the batch, for the same reason [deleteImportable] is one -- sent row by row, a
* batch could half-arrive and the rows that were missed would look exactly like rows nobody picked.
*
* Unlike an import delete this is local file removal and is done by the time it returns, so there
* is no per-row state to follow afterwards.
*/
fun deleteSubagents(settings: ServerSettings, sessionId: String, subagentIds: List<String>) {
requestFromServer(
settings,
"/sessions/$sessionId/subagents/delete",
method = "POST",
jsonBody = JSONObject().put("subagents", JSONArray(subagentIds)).toString(),
) {}
}
// What the server offers, so the spawn screen has no hardcoded lists: a setup added to the server's
// config.ron appears here with no app rebuild.
//