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
-
Add new props to MessageBubble: Accept toolCallCount (number) and turnDurationMs (number) as additional props alongside the existing activeToolCall and toolCallDisplay.
-
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.
-
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.
-
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:
- Run
/opsx:propose to generate a full proposal with specs and tasks
- Iterate on the design before any code is written
- 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
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.jsto extract and display tool call count and turn duration from the message data.Implementation approach
Add new props to MessageBubble: Accept
toolCallCount(number) andturnDurationMs(number) as additional props alongside the existingactiveToolCallandtoolCallDisplay.Pass data from MessageList: In
src/tui/messageList.js, whereMessageBubbleis rendered (line ~360), pass the accumulated tool call count and duration from the message data.Track in app.js: In
src/tui/app.js, the streaming handler already trackson_tool_startandon_tool_endevents. ThestartTimeis captured at line 210/380 (assistantTime). ThefinalizeStreamingfunction (or equivalent) should compute the duration and count tools from the events array.Render in the bubble: Add a new element below the existing
toolDisplayElthat shows:🔧 3 tools(or similar icon + count)⏱ 2.4s(formatted duration)Data flow
Rendering example
Alternatives Considered
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:
/opsx:proposeto generate a full proposal with specs and tasksAdditional Context
Codebase audit findings
Current MessageBubble props (
src/tui/messageBubble.jsline 119):role,content,topic,time,assistantName,reasoningContent,activeToolCall,toolCallDisplaytimeprop 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.jsline 121):dataRefMap with fields:id,role,content,time,reasoningContent,activeToolCall,toolCallDisplay,events,streamingeventsarray already captures all stream events includingon_tool_startandon_tool_end.Event tracking (
src/tui/app.jsline 694-750):createStreamingHandlercaptures all events viamessageListRef.current?.updateMessage(streamingMsgIdRef.current, { events: [...currentEvents, event] })on_tool_startevents are captured at line 730-737on_tool_endevents are captured at line 741-750const 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 alongsidetime(formatted string). ComputetoolCallCountby countingon_tool_startevents in the events array. ComputeturnDurationMsasDate.now() - startTimeat finalize time. Pass both as props to MessageBubble.Files to modify:
src/tui/messageBubble.js— add props, render duration/count elementssrc/tui/messageList.js— storestartTimein message data, pass new props to bubblesrc/tui/app.js— capture epoch start time, compute duration and tool count at finalize, pass to messageList