Keep a subagent's words in its own transcript, and count the ones already running

Two corrections to the previous commit.

A subagent's closing report belongs in the subagent's transcript, which is
where it already is; drawing it as a card in the parent's put the same
paragraph in two places for a reader who did not ask for it. The row is a
divider now -- a boundary, which is what the transcript actually needed there
-- closed, saying only what reported and how it went. Opening it shows the
report anyway, since leaving the conversation to read one line has its own
cost, and a backgrounded command has no transcript of its own so this is the
only place its report exists at all: that one names itself from its summary
and has nothing left to open. `TranscriptDivider` grew a `trailing` slot for
the chevron rather than the row growing its own copy of the rules.

And the status was wrong for a session that was already running before the
update, which is every session when the backend is replaced under it.
Adoption picks a session's stdout back up from a recorded offset, so the
`task_started` lines for subagents launched earlier are behind it and the
translator never saw them -- it started with an empty set and reported `idle`
with a subagent plainly still working. `Subagents::any_open` reads the
directory instead, which is a measurement rather than bookkeeping and is right
for a session this process did not start. Both sources are kept and neither
subsumes the other: the translator's own set is the only thing that knows
about a backgrounded *command*, which has no subagent to be found. The same
pair decides whether an ending has already been reported, so a task that began
before the restart still gets its divider.

Echo's helpers now record their report as their own subagent's closing text,
the way the real driver does, so the fixture has the shape being tested.

Verified on the emulator: three dividers closed, one opened to its report, and
each reply drawn as its own message. 170 server tests, ktfmt, clippy, rustfmt,
Android lint and the JVM unit tests all clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
irisandClaude Opus 5 committed 2026-09-06 19:54:36 -04:00
1 parent 5711c2568a
commit ef1aad8776
11 files changed
+342 -120

No files matched your search

+18 -6
View File
@@ -241,12 +241,24 @@ sent while a subagent runs is not held until the subagent finishes; and
`sessionWorking("waiting")` is deliberately **false** — nothing is being
written, and the fold uses that same predicate to decide a reply is settled.
- **A task reporting back is a row** (`Event::TaskNote`, `TaskNoteRow`), and
the reply that answers it is a **new** message. The fold refuses to grow a
settled reply; without that, two turns with nothing recorded between them
were folded into one and ran together mid-sentence. `./ui-sandbox.sh` plus
`/subagent 8` in an echo session is the whole rig — the helpers stagger a
second apart so each report and the reply to it are legible.
- **A task reporting back is a divider** (`Event::TaskNote`, `TaskNoteRow`),
and the reply that answers it is a **new** message. The fold refuses to grow
a settled reply; without that, two turns with nothing recorded between them
were folded into one and ran together mid-sentence. The divider is **closed**
and does not say what the subagent said: that is recorded as the subagent's
own transcript's closing text and belongs there, not repeated in its
parent's. Opening it shows the report anyway, and a backgrounded *command*
— which has no transcript of its own — names itself from its summary and has
nothing left to open. `./ui-sandbox.sh` plus `/subagent 8` in an echo session
is the whole rig; the helpers stagger a second apart so each report and the
reply to it are legible.
- **Whether work is outstanding has two sources and needs both.** The
translator's `open_tasks` is what it watched start — the only thing that
knows about a backgrounded command — and `Subagents::any_open` reads the
directory, which is the only thing that knows about a subagent started
before this translator existed. That second one is every subagent a session
has when the backend is updated under it: adoption reads stdout from a
recorded offset, so those `task_started` lines are already behind it.
- **A usage limit a subagent hits reaches the session**, not just the
subagent's own transcript; auto-resume can only schedule against a session.
That is the case where the main agent is idle and a background Task is