feat(desktop,web): subagents count how long they have worked, and a finished turn says how long it took
What
Dean: "I'm not seeing an indicator for how long an agent/subagent has been working. And did we add timestamps at the end of an assistant turn?"
Why the agent timer didn't show: the installed Luke 0.11.0 doesn't have it. The v0.11.0 tag was cut at 16bf1da6, one commit before #1986 (5f9d2fdd) landed, so no released build carries the "Working… 1m 12s" line yet. I followed the data path on main (service runStartedAt → list and transcript page → withAgentRun → AgentTab → WorkingLine; Work tab startedAt → TurnState) and it is wired end to end. The existing store tests and renderer tests cover it, and I found no bug. It will show in the next release.
This PR adds the two missing pieces:
- Subagent timers. A running subagent's row shows how long it has worked beside its spinner (
42s). Its tab's "Working…" line counts too. This covers both coding agent workers and Luke's planning workers. The existing spinner and shimmer are unchanged. - Turn-end footer. Every finished turn ends on one quiet line, "Worked for 1m 18s · 3:42 PM", with the full date and time on hover. This applies to coding agent tabs, worker tabs, and Work tab turns (and a planning subagent's tab). A running turn has no footer; it shows the live count instead.
Screenshots
Rendered from the real components in headless Chrome on Linux, not the Mac app.
| Light | Dark | |
|---|---|---|
| Running agent: finished turn footer, running subagent row, live line | ![]() | ![]() |
| Work tab: finished turn footer, running turn and worker row | ![]() | ![]() |
| Work tab, both turns finished | ![]() | ![]() |
| Running subagent tab | ![]() | ![]() |
| Finished subagent tab | ![]() | ![]() |
How
No migration. Every time needed is already in the database.
- Coding agent transcripts (the agent's own and its workers'): a turn's journal row on the wire now carries
startedAtandfinishedAt.startedAtis the turn's start, or its queueing before eve starts it, the same rulerunStartedAtuses, so the footer agrees with the live count (Devin's finding: the journal itself opens a moment later, with the first step).finishedAtis the journal's close, written together with its last parts, so the page that delivers the final parts delivers the end too. Reading the turn row'ssettled_atinstead could land a page later than those parts. - Running coding-agent workers: the agent's page adds
delegations: [{ callId, startedAt }]. It is computed once per page from the delegate calls still unanswered in the newest turn's journal. EachstartedAtis when the delegation's child conversation was opened (delegationStartsOf). The newest turn's journal is read because the parent journal is not amended while a worker runs. Only a page computed this way (including the empty page at the end of a hold) can name a worker whose record landed after its call was journaled. A worker's own page already carriedrunStartedAt, and its tab now reads it. - Work tab:
PlanWorkTurn.endedAtis the turn row'ssettled_atonce the turn is no longer running. A planning worker'ssessionaddsstartedAtandendedAt, read from its newest task's journal. This works the same in the record read (plan-work-store.ts) and the live call path (live-brain.ts). - Desktop:
working-time.tsxgainsworkedForTextandWorkedFor.WorkingTimetakes an optional class so it can stand in a row.TranscriptSubagenttakes astartedAt. Every count still reads the one shared clock atom, and each is gated on the tab being on screen, as in #1986.
All new wire keys are optional. An older service just shows no count and no footer. The JSON Schema goldens were re-recorded under LUKE_UPDATE_FIXTURES=1, and the only diffs are the new optional keys. No model is shown these schemas.
Tests
apps/web/tests/coding-agents-app.test.ts(PGlite, TestClock):- A journal row carries its turn's start (ahead of the journal opening) while it is written, then its
finishedAtonce answered. - The agent's page names no delegation before its record lands, names the record's start while the call runs, and names none once the call answers.
- A journal row carries its turn's start (ahead of the journal opening) while it is written, then its
apps/web/tests/plan-work.test.ts(PGlite):- A running turn has no
endedAt, and a finished turn carries its end. - A worker session carries its task's start while running, then its end too.
- A running turn has no
working-time.test.ts(TestClock):- The footer's text.
- An agent's finished turn ends on the footer with the full moment on hover, and a turn still being written has none.
- A running worker row counts from the page's start and stops when its call answers.
- The Work tab counts its running turn and worker only while shown, and a finished turn ends on the footer.
- A planning subagent tab counts while running, then shows the footer.
use-agent-transcript.test.ts: a page's run start and its delegations reach the tab, and a page naming none clears them.
Mac verification
CI could not verify the Mac UI, and ./scripts/verify.sh was not run: this was built on a Linux cloud VM, and CI builds nothing for the Mac (LUKE-159 is the manual Mac pass). ./scripts/check.sh passes. The web side needs a deploy before the subagent counts and footers appear.
🤖 Generated with Claude Code









