Skip to content

feat: display tool call count and turn duration in MessageBubble #690

Description

@avoidwork

Summary

Add tool call count and turn duration display to the MessageBubble component in the TUI. Each assistant message bubble will show how many tools were called during that turn and the elapsed time from start to finish.

Motivation

Users need visibility into agent activity — specifically, how many tools were called per turn and how long each turn took. Currently, the MessageBubble only shows the role label, timestamp, content, reasoning, active tool call, and tool call display output. There is no aggregate metadata about the turn itself. This makes it difficult to assess agent efficiency or diagnose slow turns.

Proposed Solution

Update src/tui/messageBubble.js to extract and display tool call count and turn duration from the message data.

Implementation approach

  1. Add new props to MessageBubble: Accept toolCallCount (number) and turnDurationMs (number) as additional props alongside the existing activeToolCall and toolCallDisplay.

  2. Pass data from MessageList: In src/tui/messageList.js, where MessageBubble is rendered (line ~360), pass the accumulated tool call count and duration from the message data.

  3. Track in app.js: In src/tui/app.js, the streaming handler already tracks on_tool_start and on_tool_end events. The startTime is captured at line 210/380 (assistantTime). The finalizeStreaming function (or equivalent) should compute the duration and count tools from the events array.

  4. Render in the bubble: Add a new element below the existing toolDisplayEl that shows:

    • 🔧 3 tools (or similar icon + count)
    • ⏱ 2.4s (formatted duration)

Data flow

app.js (on_tool_start/end events)
  → messageList.js (store in events array + compute count/duration)
    → messageBubble.js (render count + duration)

Rendering example

<Box flexDirection="row" marginTop={1} marginLeft={2}>
  <Text dimColor color="gray">
    🔧 3 tools · ⏱ 2.4s
  </Text>
</Box>

Alternatives Considered

  • Display in a status bar instead of the bubble: Would require a new UI component and shared state. Keeping it in the bubble is simpler and keeps metadata co-located with the message.
  • Show per-tool timing: Would require tracking per-tool start/end timestamps. Out of scope for this iteration — aggregate turn duration is sufficient.
  • Compute duration in the bubble component: The bubble only receives the final timestamp (time), not the start time. Computing duration requires the start time from app.js, so it is better computed upstream and passed as a prop.

OpenSpec Note

This project uses OpenSpec for feature development. If this request is approved, I will:

  1. Run /opsx:propose to generate a full proposal with specs and tasks
  2. Iterate on the design before any code is written
  3. Follow the task-driven implementation workflow

Additional Context

Codebase audit findings

Current MessageBubble props (src/tui/messageBubble.js line 119):

  • role, content, topic, time, assistantName, reasoningContent, activeToolCall, toolCallDisplay
  • The time prop is a HH:MM timestamp string — not a Date object, so duration cannot be computed from the bubble alone.

Current data storage (src/tui/messageList.js line 121):

  • Message data stored in dataRef Map with fields: id, role, content, time, reasoningContent, activeToolCall, toolCallDisplay, events, streaming
  • The events array already captures all stream events including on_tool_start and on_tool_end.

Event tracking (src/tui/app.js line 694-750):

  • createStreamingHandler captures all events via messageListRef.current?.updateMessage(streamingMsgIdRef.current, { events: [...currentEvents, event] })
  • on_tool_start events are captured at line 730-737
  • on_tool_end events are captured at line 741-750
  • The start time is captured at line 210/380: const assistantTime = getTimestamp() — but this is only a HH:MM string, not a Date/epoch.

Key gap: The start time is stored as a formatted HH:MM string (getTimestamp() at line 667), not as an epoch timestamp. To compute duration, we need to either:
a) Store the epoch start time alongside the formatted time, or
b) Compute duration in app.js at finalize time and pass it as a prop.

Recommended approach: Store startTime (epoch ms) in the message data alongside time (formatted string). Compute toolCallCount by counting on_tool_start events in the events array. Compute turnDurationMs as Date.now() - startTime at finalize time. Pass both as props to MessageBubble.

Files to modify:

  • src/tui/messageBubble.js — add props, render duration/count elements
  • src/tui/messageList.js — store startTime in message data, pass new props to bubble
  • src/tui/app.js — capture epoch start time, compute duration and tool count at finalize, pass to messageList

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions