Skip to content

feat: render stream events in TUI message bubbles - #653

Open
avoidwork wants to merge 11 commits into
mainfrom
feat/render-stream-events-message-bubble
Open

feat: render stream events in TUI message bubbles#653
avoidwork wants to merge 11 commits into
mainfrom
feat/render-stream-events-message-bubble

Conversation

@avoidwork

Copy link
Copy Markdown
Owner

Summary

Render the captured LangChain streamEvents in the TUI message bubbles so users can see what's actually happening during agent execution — tool calls, agent actions, chain events, reasoning, errors.

Changes

  • src/tui/messageBubble.js — Added events prop destructuring, built eventsEl with conditional rendering (Box wrapper + Text elements for each event), rendered alongside reasoningEl, toolCallEl, toolDisplayEl
  • openspec/ — Created proposal, tasks, and spec for the change

Implementation Details

The events rendering follows the existing pattern in MessageBubble:

  • Conditional check: hasEvents = events && events.length > 0
  • Box wrapper with flexDirection: "column" and marginTop: 1
  • Header line showing event count
  • Mapped event lines showing type and name

Related

Closes #604

- Add events prop to MessageBubble component
- Build eventsEl following existing rendering pattern (Box wrapper, Text elements)
- Render events alongside reasoning, tool call, and tool display elements
- Add OpenSpec proposal, tasks, and spec for the change

Closes #604
@avoidwork

Copy link
Copy Markdown
Owner Author

PR Audit: component-message-bubbles Spec Compliance

PR: #653 — feat: render stream events in TUI message bubbles
Spec: openspec/specs/component-message-bubbles/spec.md (delta spec synced to main)
Date: 2026-07-28


Requirement 1: MessageBubble renders stream events

The MessageBubble component SHALL conditionally render stream events when the events prop is present and non-empty. Events are rendered as a collapsible section below the main content, with each event displayed as a labeled line showing the event type and name.

Scenario Status Notes
Renders events when present ✅ PASS hasEvents guard; header + event lines displayed
Renders no events when absent ✅ PASS eventsEl is null when events is falsy or empty
Renders events in order ✅ PASS .map() preserves array order; keys use index
Labeled line with type and name ✅ PASS Format: - ${evt.type} (${evt.name})
Collapsible section ❌ FAIL Spec requires collapsible section. Implementation renders a static list with no collapse/expand mechanism.

Verdict: 4/5 pass. 1 gap.


Requirement 2: Event data structure

Each event object has the shape { type, name, data, tags, metadata }. The rendering SHALL use type and name for display, with optional data content when present.

Scenario Status Notes
Uses type for display ✅ PASS evt.type rendered
Uses name for display ✅ PASS evt.name rendered when present
Optional data content ❌ FAIL data field is not rendered. Spec says "optional data content when present."

Verdict: 2/3 pass. 1 gap.


Requirement 3: Events rendering follows existing pattern

The events section SHALL follow the same rendering pattern as reasoningEl, toolCallEl, and toolDisplayEl: conditional rendering, Box wrapper with flexDirection: "row", marginTop: 1, marginLeft: 2, and Text elements with appropriate styling.

Scenario Status Notes
Conditional rendering ✅ PASS hasEvents check gates eventsEl
Box wrapper with marginTop: 1, marginLeft: 2 ✅ PASS Both present
Text elements with styling ✅ PASS dimColor: true, color: "gray"
flexDirection: "row" ❌ FAIL Spec requires "row". Implementation uses "column" (line 205).

Verdict: 3/4 pass. 1 gap.


Summary

Requirement Scenarios Pass Fail
1. MessageBubble renders stream events 5 4 1
2. Event data structure 3 2 1
3. Events rendering follows existing pattern 4 3 1
Total 12 9 3

Gaps to Address

  1. Collapsible section (Req 1) — Spec requires collapsible behavior. Ink supports wrap="never" but true collapse/expand would need state management (e.g., a collapsed boolean toggled by user input). This is a feature gap, not a bug.

  2. data field rendering (Req 2) — Spec says "optional data content when present." The implementation ignores data entirely. Could be addressed by conditionally rendering data content (e.g., stringified or truncated) when present.

  3. flexDirection: "row" mismatch (Req 3) — Spec explicitly requires "row". The implementation uses "column" to stack the header and event lines vertically. This is a deliberate design choice for readability with potentially many events, but it deviates from the spec. If "column" is preferred, the spec should be updated.

Overall Assessment

The implementation is functionally complete and well-structured. The three gaps are minor deviations — one is a missing interactive feature (collapsible), one is an unrendered optional field (data), and one is a deliberate layout choice that conflicts with the spec text. All three are addressable without architectural changes.

- Add collapsible toggle for events section (+/- indicator)
- Render optional data field when present (truncated to 100 chars)
- Change flexDirection from 'column' to 'row' to match spec pattern
Text doesn't support onClick in Ink — wrap the toggle label in a
Box with cursorHidden and onClick on the Box instead.
Ink's onClick is unreliable and collapsible sections are a desktop
UI pattern, not a terminal one. Events are now always visible.
Instead of listing every event, show one line per type with count:
  - message [16]
  - tool_call [3]
  - error [1]
Raw event types like 'on_tool_start' are now mapped to human-readable
labels: tool, model, agent, chain, retriever, custom. Unknown types
are cleaned up (strips 'on_' prefix, replaces underscores).
Previously the provider used streamMode 'messages' which only emitted
message events. Now it uses 'events' to capture the full stream:
on_tool_start, on_tool_end, on_chat_model_stream, on_agent_action, etc.

Events are transformed from LangChain format (event.event) to the
streamingCallback format (event.type) so the TUI handler works
without changes.
@avoidwork

Copy link
Copy Markdown
Owner Author

I'm pretty sure this isn't technically possible without patching the deepagentsjs lib to emit more than a 'message' event.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: render stream events in TUI message bubbles

1 participant