feat(desktop,web): a working agent's line counts how long its run has gone

Merged
DA
8d ago
+522-63

What

While a coding agent works, its tab's last line now counts how long the run has been going: "Working… 1m 12s", ticking once a second. The Work tab's running turn counts the same way after its state ("Working 3m 4s").

The format is the compact one Codex and Cursor use: 42s, 1m 12s, 1h 3m.

Screenshots

Rendered from the real components in headless Chrome on Linux, light theme. This is not the Mac app.

Agent tabWork tab running turn
agent tabwork tab

How

  • Wire/service: CodingAgentSummary gains an optional runStartedAt (epoch ms) for when the current run began. The service fills it from the newest turn's start, or from when the turn was queued if eve has not started it yet. Before the agent's first turn it uses the agent's own start. It is left out while a follow-up message is still waiting for its turn, because that run has not begun. An older service never sends it, and the desktop then shows plain "Working…". latestTurnsOf now also reads queued_at and started_at.

  • The transcript page carries it too. The messages answer gains the same optional runStartedAt. The desktop updates an agent's status and run start together (withAgentRun) from every page, Stop, and message answer. Without this, an open tab following a new turn would go on counting from the old run until the list was read again (Devin's finding).

  • Desktop: one shared module, planning/working-time.tsx, holds:

    • workingTime, the formatter
    • workingClock, a Stream.fromEffectSchedule(Clock.currentTimeMillis, Schedule.spaced(1s))
    • one atom on the renderer runtime that every count reads
    • WorkingTime and WorkingLine

    There is no raw setInterval. The atom runs only while a count is mounted, so a run that ended, a tab that closed, a tab off screen (shown gates the agent tab and the Work tab), or a line with no known start leaves nothing ticking. The count is role="timer", so it is not read aloud every second inside the line's polite live region.

Skipped (agreed with the orchestrator)

  • Subagent tabs (coding agent workers and Luke's planning workers) stay plain "Working…". No start time for a subagent reaches the window. Adding one means stamping the delegation's start in both the coder host and plan-work-store. They already use the shared WorkingLine, so that change only needs to pass a startedAt.
  • The "+" list's agent rows carry no count, per Dean. Their status word and dot are left as on main, since the "+ picker polish" PR removes them.
  • "Worked for 1m 18s" when a run finishes: no end time reaches the desktop. WorkingTime is where it would go.

Tests

  • apps/web/tests/coding-agents-app.test.ts (real store, PGlite): in both the list and the transcript page, runStartedAt uses the agent's start before any turn, then the turn's queueing, then its start. It is absent while a message waits for its turn, and is the new turn's start once that turn begins.
  • apps/desktop/src/renderer/planning/working-time.test.ts (TestClock; the registry's runtime is seeded with the test's own context, so the mounted line counts on the clock the test moves):
    • the formatter across the minute and hour boundaries
    • the clock ticks once a second
    • a mounted line counts up across the minute
    • a line with no start shows no count
    • the agent transcript's count goes away once the run ends
    • the Work tab counts only a running turn on a shown tab
  • use-coding-agents.test.ts and use-agent-transcript.test.ts: a page's run start reaches the agent list, moves to a new turn's start, and is cleared while a message waits for its turn.
  • JSON Schema goldens for the seven coding agent wire schemas were re-recorded under LUKE_UPDATE_FIXTURES=1. The only change is the new optional runStartedAt key, which no model is shown.

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 count appears on coding agent tabs.

🤖 Generated with Claude Code