feat(desktop,web): subagents count how long they have worked, and a finished turn says how long it took

Merged
DA
8d ago
+915-94

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:

  1. 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.
  2. 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.

LightDark
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 startedAt and finishedAt. startedAt is the turn's start, or its queueing before eve starts it, the same rule runStartedAt uses, so the footer agrees with the live count (Devin's finding: the journal itself opens a moment later, with the first step). finishedAt is 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's settled_at instead 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. Each startedAt is 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 carried runStartedAt, and its tab now reads it.
  • Work tab: PlanWorkTurn.endedAt is the turn row's settled_at once the turn is no longer running. A planning worker's session adds startedAt and endedAt, 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.tsx gains workedForText and WorkedFor. WorkingTime takes an optional class so it can stand in a row. TranscriptSubagent takes a startedAt. 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 finishedAt once 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.
  • 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.
  • 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