Skip to content

Commit 848ffbf

Browse files
authored
Merge pull request #15 from damassi/refactor/simplify-message-bus
refactor: simplify message rendering
2 parents 270f393 + 24354b0 commit 848ffbf

File tree

12 files changed

+301
-217
lines changed

12 files changed

+301
-217
lines changed

docs/architecture.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ src/agent_chat_cli/
1010
├── core/
1111
│ ├── actions.py # User action handlers
1212
│ ├── agent_loop.py # Claude Agent SDK client wrapper
13-
│ ├── message_bus.py # Message routing from agent to UI
13+
│ ├── renderer.py # Message routing from agent to UI
1414
│ ├── ui_state.py # Centralized UI state management
1515
│ └── styles.tcss # Textual CSS styles
1616
├── components/
@@ -43,7 +43,7 @@ The application follows a loosely coupled architecture with four main orchestrat
4343
┌─────────────────────────────────────────────────────────────┐
4444
│ AgentChatCLIApp │
4545
│ ┌───────────┐ ┌───────────┐ ┌─────────┐ ┌───────────┐ │
46-
│ │ UIState │ │MessageBus │ │ Actions │ │ AgentLoop │ │
46+
│ │ UIState │ │ Renderer │ │ Actions │ │ AgentLoop │ │
4747
│ └─────┬─────┘ └─────┬─────┘ └────┬────┘ └─────┬─────┘ │
4848
│ │ │ │ │ │
4949
│ └──────────────┴─────────────┴──────────────┘ │
@@ -63,9 +63,9 @@ Centralized management of UI state behaviors. Handles:
6363
- Tool permission prompt display/hide
6464
- Interrupt state tracking
6565

66-
This class was introduced in PR #9 to consolidate scattered UI state logic from Actions and MessageBus into a single cohesive module.
66+
This class was introduced in PR #9 to consolidate scattered UI state logic from Actions and Renderer into a single cohesive module.
6767

68-
**MessageBus** (`core/message_bus.py`)
68+
**Renderer** (`core/renderer.py`)
6969
Routes messages from the AgentLoop to appropriate UI components:
7070
- `STREAM_EVENT`: Streaming text chunks to AgentMessage widgets
7171
- `ASSISTANT`: Complete assistant responses with tool use blocks
@@ -92,7 +92,7 @@ Manages the Claude Agent SDK client lifecycle:
9292
1. User types in `UserInput` and presses Enter
9393
2. `Actions.submit_user_message()` posts to UI and enqueues to `AgentLoop.query_queue`
9494
3. `AgentLoop` sends query to Claude Agent SDK and streams responses
95-
4. Responses flow through `MessageBus.handle_agent_message()` to update UI
95+
4. Responses flow through `Actions.render_message()` to update UI
9696
5. Tool use triggers permission prompt via `UIState.show_permission_prompt()`
9797
6. User response flows back through `Actions.respond_to_tool_permission()`
9898

@@ -118,7 +118,7 @@ Modal prompt for tool permission requests:
118118
- Manages focus to prevent input elsewhere while visible
119119

120120
**ChatHistory** (`components/chat_history.py`)
121-
Container for message widgets, handles `MessagePosted` events.
121+
Container for message widgets.
122122

123123
**ThinkingIndicator** (`components/thinking_indicator.py`)
124124
Animated indicator shown during agent processing.

src/agent_chat_cli/app.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
from textual.binding import Binding
66

77
from agent_chat_cli.components.header import Header
8-
from agent_chat_cli.components.chat_history import ChatHistory, MessagePosted
8+
from agent_chat_cli.components.chat_history import ChatHistory
99
from agent_chat_cli.components.thinking_indicator import ThinkingIndicator
1010
from agent_chat_cli.components.tool_permission_prompt import ToolPermissionPrompt
1111
from agent_chat_cli.components.user_input import UserInput
1212
from agent_chat_cli.core.agent_loop import AgentLoop
13-
from agent_chat_cli.core.message_bus import MessageBus
13+
from agent_chat_cli.core.renderer import Renderer
1414
from agent_chat_cli.core.actions import Actions
1515
from agent_chat_cli.core.ui_state import UIState
1616
from agent_chat_cli.utils.logger import setup_logging
@@ -34,7 +34,7 @@ def __init__(self) -> None:
3434
super().__init__()
3535

3636
self.ui_state = UIState(app=self)
37-
self.message_bus = MessageBus(app=self)
37+
self.renderer = Renderer(app=self)
3838
self.actions = Actions(app=self)
3939
self.agent_loop = AgentLoop(app=self)
4040

@@ -49,9 +49,6 @@ def compose(self) -> ComposeResult:
4949
async def on_mount(self) -> None:
5050
asyncio.create_task(self.agent_loop.start())
5151

52-
async def on_message_posted(self, event: MessagePosted) -> None:
53-
await self.message_bus.on_message_posted(event)
54-
5552
async def action_interrupt(self) -> None:
5653
await self.actions.interrupt()
5754

src/agent_chat_cli/components/chat_history.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import json
22
from textual.containers import Container
3-
from textual.message import Message as TextualMessage
43

54
from agent_chat_cli.components.messages import (
65
AgentMessage,
@@ -48,9 +47,3 @@ def _create_message_widget(
4847
tool_widget.tool_input = {"raw": message.content}
4948

5049
return tool_widget
51-
52-
53-
class MessagePosted(TextualMessage):
54-
def __init__(self, message: Message) -> None:
55-
self.message = message
56-
super().__init__()

src/agent_chat_cli/core/actions.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
from typing import TYPE_CHECKING
22

33
from agent_chat_cli.utils.enums import ControlCommand
4-
from agent_chat_cli.components.chat_history import ChatHistory, MessagePosted
5-
from agent_chat_cli.components.messages import Message
4+
from agent_chat_cli.components.chat_history import ChatHistory
5+
from agent_chat_cli.components.messages import Message, MessageType
66
from agent_chat_cli.components.tool_permission_prompt import ToolPermissionPrompt
77
from agent_chat_cli.utils.logger import log_json
88

@@ -17,16 +17,32 @@ def __init__(self, app: "AgentChatCLIApp") -> None:
1717
def quit(self) -> None:
1818
self.app.exit()
1919

20+
async def add_message_to_chat(self, type: MessageType, content: str) -> None:
21+
match type:
22+
case MessageType.USER:
23+
message = Message.user(content)
24+
case MessageType.SYSTEM:
25+
message = Message.system(content)
26+
case MessageType.AGENT:
27+
message = Message.agent(content)
28+
case _:
29+
raise ValueError(f"Unsupported message type: {type}")
30+
31+
chat_history = self.app.query_one(ChatHistory)
32+
chat_history.add_message(message)
33+
2034
async def submit_user_message(self, message: str) -> None:
21-
self.app.post_message(MessagePosted(Message.user(message)))
35+
chat_history = self.app.query_one(ChatHistory)
36+
chat_history.add_message(Message.user(message))
2237
self.app.ui_state.start_thinking()
38+
await self.app.ui_state.scroll_to_bottom()
2339
await self._query(message)
2440

25-
def post_system_message(self, message: str) -> None:
26-
self.app.post_message(MessagePosted(Message.system(message)))
41+
async def post_system_message(self, message: str) -> None:
42+
await self.add_message_to_chat(MessageType.SYSTEM, message)
2743

28-
async def handle_agent_message(self, message) -> None:
29-
await self.app.message_bus.handle_agent_message(message)
44+
async def render_message(self, message) -> None:
45+
await self.app.renderer.render_message(message)
3046

3147
async def interrupt(self) -> None:
3248
permission_prompt = self.app.query_one(ToolPermissionPrompt)

src/agent_chat_cli/core/agent_loop.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ async def start(self) -> None:
9292

9393
await self._handle_message(message)
9494

95-
await self.app.actions.handle_agent_message(
95+
await self.app.actions.render_message(
9696
AgentMessage(type=AgentMessageType.RESULT, data=None)
9797
)
9898

@@ -135,7 +135,7 @@ async def _handle_message(self, message: Message) -> None:
135135
text_chunk = delta.get("text", "")
136136

137137
if text_chunk:
138-
await self.app.actions.handle_agent_message(
138+
await self.app.actions.render_message(
139139
AgentMessage(
140140
type=AgentMessageType.STREAM_EVENT,
141141
data={"text": text_chunk},
@@ -163,7 +163,7 @@ async def _handle_message(self, message: Message) -> None:
163163
)
164164

165165
# Finally, post the agent assistant response
166-
await self.app.actions.handle_agent_message(
166+
await self.app.actions.render_message(
167167
AgentMessage(
168168
type=AgentMessageType.ASSISTANT,
169169
data={"content": content},
@@ -180,7 +180,7 @@ async def _can_use_tool(
180180

181181
# Handle permission request queue sequentially
182182
async with self.permission_lock:
183-
await self.app.actions.handle_agent_message(
183+
await self.app.actions.render_message(
184184
AgentMessage(
185185
type=AgentMessageType.TOOL_PERMISSION_REQUEST,
186186
data={
@@ -213,7 +213,7 @@ async def _can_use_tool(
213213
)
214214

215215
if rejected_tool:
216-
self.app.actions.post_system_message(
216+
await self.app.actions.post_system_message(
217217
f"Permission denied for {tool_name}"
218218
)
219219

src/agent_chat_cli/core/message_bus.py

Lines changed: 0 additions & 149 deletions
This file was deleted.

0 commit comments

Comments
 (0)