chat: route_entry helper separates routing from sync

PaneTarget enum + route_entry() function: given a ConversationEntry,
returns which pane it belongs to (or None to skip). The sync loop
becomes: detect desync → pop, then route new entries.

Routing: User→Conversation, Assistant→ConversationAssistant,
tool_calls→Tools, Tool results→ToolResult, Memory/System→None.

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
Kent Overstreet 2026-04-05 19:27:14 -04:00
parent b89bafdf6b
commit 563771e979

View file

@ -18,6 +18,53 @@ use super::{
};
use crate::user::ui_channel::{UiMessage, StreamTarget};
enum PaneTarget {
Conversation,
ConversationAssistant,
Tools,
ToolResult,
}
/// Route an agent entry to the appropriate pane.
/// Returns None for entries that shouldn't be displayed (memory, system).
fn route_entry(entry: &crate::agent::context::ConversationEntry) -> Option<(PaneTarget, String, Marker)> {
use crate::agent::api::types::Role;
use crate::agent::context::ConversationEntry;
if let ConversationEntry::Memory { .. } = entry {
return None;
}
let msg = entry.message();
let text = msg.content_text().to_string();
match msg.role {
Role::User => {
if text.starts_with("<system-reminder>") { return None; }
Some((PaneTarget::Conversation, text, Marker::User))
}
Role::Assistant => {
// Tool calls → tools pane
if let Some(ref calls) = msg.tool_calls {
for call in calls {
let line = format!("[{}] {}",
call.function.name,
call.function.arguments.chars().take(80).collect::<String>());
// TODO: return multiple targets — for now just return first tool call
return Some((PaneTarget::Tools, line, Marker::None));
}
}
if text.is_empty() { return None; }
Some((PaneTarget::ConversationAssistant, text, Marker::Assistant))
}
Role::Tool => {
if text.is_empty() { return None; }
Some((PaneTarget::ToolResult, text, Marker::None))
}
Role::System => None,
}
}
pub(crate) struct InteractScreen {
pub(crate) autonomous: PaneState,
pub(crate) conversation: PaneState,
@ -61,69 +108,38 @@ impl InteractScreen {
/// Sync conversation display from agent entries.
fn sync_from_agent(&mut self) {
use crate::agent::api::types::Role;
let agent = self.agent.blocking_lock();
let gen = agent.generation;
let count = agent.entries().len();
if gen != self.last_generation {
// Generation changed — full re-render
// Phase 1: detect desync — pop entries from panes to match
if gen != self.last_generation || count < self.last_entry_count {
// Compaction or mutation happened — full reset
// (could be smarter and pop from front, but reset is safe)
self.conversation = PaneState::new(true);
self.autonomous = PaneState::new(true);
self.tools = PaneState::new(false);
self.last_entry_count = 0;
}
// Render new entries
// Phase 2: route new entries to panes
for entry in agent.entries().iter().skip(self.last_entry_count) {
let msg = entry.message();
let text = msg.content_text();
// Skip memory/system-reminder entries — they're context, not conversation
if let crate::agent::context::ConversationEntry::Memory { .. } = entry {
continue;
match route_entry(entry) {
Some((PaneTarget::Conversation, text, marker)) => {
self.conversation.push_line_with_marker(text, Color::Cyan, marker);
}
match msg.role {
Role::User => {
// Skip system-reminder injections
if text.starts_with("<system-reminder>") {
continue;
Some((PaneTarget::ConversationAssistant, text, marker)) => {
self.conversation.push_line_with_marker(text, Color::Reset, marker);
}
self.conversation.push_line_with_marker(
text.to_string(), Color::Cyan, Marker::User,
);
Some((PaneTarget::Tools, text, _)) => {
self.tools.push_line(text, Color::Yellow);
}
Role::Assistant => {
// Tool calls show in tools pane
if let Some(ref calls) = msg.tool_calls {
for call in calls {
let line = format!("[{}] {}", call.function.name,
call.function.arguments.chars().take(80).collect::<String>());
self.tools.push_line(line, Color::Yellow);
}
}
// Text content to conversation
if !text.is_empty() {
self.conversation.push_line_with_marker(
text.to_string(), Color::Reset, Marker::Assistant,
);
}
}
Role::Tool => {
// Tool results to tools pane
Some((PaneTarget::ToolResult, text, _)) => {
for line in text.lines().take(20) {
self.tools.push_line(format!(" {}", line), Color::DarkGray);
}
if text.lines().count() > 20 {
self.tools.push_line(
format!(" ... ({} more lines)", text.lines().count() - 20),
Color::DarkGray,
);
}
}
Role::System => {} // skip
None => {} // skip (memory, system, system-reminder)
}
}