Kill UiMessage variants replaced by state-driven rendering

sync_from_agent reads directly from agent entries, so remove:
- UserInput (pending input shown from MindState.input)
- ToolCall, ToolResult (shown from entries on completion)
- ToolStarted, ToolFinished (replaced by shared active_tools)
- replay_session_to_ui (sync_from_agent handles replay)

-139 lines. Remaining variants are streaming (TextDelta, Reasoning),
status bar state, or ephemeral UI messages (Info, Debug).

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
ProofOfConcept 2026-04-05 21:21:08 -04:00
parent 306788e0f1
commit 1745e03550
5 changed files with 5 additions and 139 deletions

View file

@ -651,10 +651,6 @@ pub async fn collect_stream(
let args: serde_json::Value = let args: serde_json::Value =
serde_json::from_str(&call.function.arguments).unwrap_or_default(); serde_json::from_str(&call.function.arguments).unwrap_or_default();
let args_summary = summarize_args(&call.function.name, &args); let args_summary = summarize_args(&call.function.name, &args);
let _ = ui_tx.send(UiMessage::ToolCall {
name: call.function.name.clone(),
args_summary: args_summary.clone(),
});
let is_background = args.get("run_in_background") let is_background = args.get("run_in_background")
.and_then(|v| v.as_bool()) .and_then(|v| v.as_bool())
.unwrap_or(false); .unwrap_or(false);

View file

@ -510,10 +510,6 @@ impl Agent {
let args_summary = summarize_args(&call.function.name, &args); let args_summary = summarize_args(&call.function.name, &args);
let _ = ui_tx.send(UiMessage::Activity(format!("calling: {}", call.function.name))); let _ = ui_tx.send(UiMessage::Activity(format!("calling: {}", call.function.name)));
let _ = ui_tx.send(UiMessage::ToolCall {
name: call.function.name.clone(),
args_summary: args_summary.clone(),
});
// Spawn tool, track it // Spawn tool, track it
let call_clone = call.clone(); let call_clone = call.clone();
@ -561,10 +557,6 @@ impl Agent {
ds.tool_errors += 1; ds.tool_errors += 1;
} }
let _ = ui_tx.send(UiMessage::ToolResult {
name: call.function.name.clone(),
result: output.clone(),
});
self.active_tools.lock().unwrap().retain(|t| t.id != call.id); self.active_tools.lock().unwrap().retain(|t| t.id != call.id);
// Tag memory_render results for context deduplication // Tag memory_render results for context deduplication

View file

@ -602,24 +602,6 @@ impl InteractScreen {
self.autonomous.append_text(text); self.autonomous.append_text(text);
} }
}, },
UiMessage::UserInput(text) => {
self.conversation.push_line_with_marker(text.clone(), Color::Cyan, Marker::User);
self.turn_started = Some(std::time::Instant::now());
self.needs_assistant_marker = true;
app.status.turn_tools = 0;
}
UiMessage::ToolCall { name, args_summary } => {
app.status.turn_tools += 1;
let line = if args_summary.is_empty() { format!("[{}]", name) }
else { format!("[{}] {}", name, args_summary) };
self.tools.push_line(line, Color::Yellow);
}
UiMessage::ToolResult { result, .. } => {
for line in result.lines() {
self.tools.push_line(format!(" {}", line), Color::DarkGray);
}
self.tools.push_line(String::new(), Color::Reset);
}
UiMessage::DmnAnnotation(text) => { UiMessage::DmnAnnotation(text) => {
self.autonomous.push_line(text.clone(), Color::Yellow); self.autonomous.push_line(text.clone(), Color::Yellow);
self.turn_started = Some(std::time::Instant::now()); self.turn_started = Some(std::time::Instant::now());

View file

@ -459,13 +459,10 @@ pub async fn run(
idle_state.decay_ewma(); idle_state.decay_ewma();
app.update_idle(&idle_state); app.update_idle(&idle_state);
// One-time: replay conversation after Mind init // One-time: mark startup done after Mind init
if !startup_done { if !startup_done {
if let Ok(ag) = agent.try_lock() { if let Ok(ag) = agent.try_lock() {
if !ag.entries().is_empty() { // sync_from_agent handles conversation replay
ui_channel::replay_session_to_ui(ag.entries(), &ui_tx);
let _ = ui_tx.send(UiMessage::Info("--- restored from conversation log ---".into()));
}
let _ = ui_tx.send(UiMessage::Info(format!(" model: {}", ag.model()))); let _ = ui_tx.send(UiMessage::Info(format!(" model: {}", ag.model())));
startup_done = true; startup_done = true;
dirty = true; dirty = true;

View file

@ -71,54 +71,28 @@ pub struct ContextInfo {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[allow(dead_code)] #[allow(dead_code)]
pub enum UiMessage { pub enum UiMessage {
/// Streaming text delta — routed to conversation or autonomous pane /// Streaming text delta — routed to conversation or autonomous pane.
/// based on the current StreamTarget.
TextDelta(String, StreamTarget), TextDelta(String, StreamTarget),
/// User's input echoed to conversation pane.
UserInput(String),
/// Tool call header: [tool_name] with args summary.
ToolCall {
name: String,
args_summary: String,
},
/// Full tool result — goes to tools pane.
ToolResult {
name: String,
result: String,
},
/// DMN state annotation: [dmn: foraging (3/20)]. /// DMN state annotation: [dmn: foraging (3/20)].
DmnAnnotation(String), DmnAnnotation(String),
/// Status bar update. /// Status bar update.
StatusUpdate(StatusInfo), StatusUpdate(StatusInfo),
/// Live activity indicator for the status bar — shows what the /// Live activity indicator for the status bar.
/// agent is doing right now ("thinking...", "calling: bash", etc).
/// Empty string clears the indicator.
Activity(String), Activity(String),
/// Reasoning/thinking tokens from the model (internal monologue). /// Reasoning/thinking tokens from the model (internal monologue).
/// Routed to the autonomous pane so the user can peek at what
/// the model is thinking about during long tool chains.
Reasoning(String), Reasoning(String),
/// A tool call started — shown as a live overlay above the status bar.
ToolStarted { id: String, name: String, detail: String },
/// A tool call finished — removes it from the live overlay.
ToolFinished { id: String },
/// Debug message (only shown when POC_DEBUG is set). /// Debug message (only shown when POC_DEBUG is set).
Debug(String), Debug(String),
/// Informational message — goes to conversation pane (command output, etc). /// Informational message — goes to conversation pane (command output, etc).
Info(String), Info(String),
/// Context loading details — stored for the debug screen (Ctrl+D). /// Context loading details — stored for the debug screen.
ContextInfoUpdate(ContextInfo), ContextInfoUpdate(ContextInfo),
/// Agent cycle state update — refreshes the F2 agents screen. /// Agent cycle state update — refreshes the F2 agents screen.
@ -157,78 +131,3 @@ pub fn channel() -> (UiSender, UiReceiver) {
/// Replay a restored session into the TUI panes so the user can see /// Replay a restored session into the TUI panes so the user can see
/// conversation history immediately on restart. Shows user input, /// conversation history immediately on restart. Shows user input,
/// assistant responses, and brief tool call summaries. Skips the system
/// prompt, context message, DMN plumbing, and image injection messages.
pub fn replay_session_to_ui(entries: &[crate::agent::context::ConversationEntry], ui_tx: &UiSender) {
use crate::agent::api::types::Role;
crate::dbglog!("[replay] replaying {} entries to UI", entries.len());
for (i, e) in entries.iter().enumerate() {
let m = e.message();
let preview: String = m.content_text().chars().take(60).collect();
crate::dbglog!("[replay] [{}] {:?} mem={} tc={} tcid={:?} {:?}",
i, m.role, e.is_memory(), m.tool_calls.as_ref().map_or(0, |t| t.len()),
m.tool_call_id.as_deref(), preview);
}
let mut seen_first_user = false;
let mut target = StreamTarget::Conversation;
for entry in entries {
if entry.is_memory() { continue; }
let msg = entry.message();
match msg.role {
Role::System => {}
Role::User => {
if !seen_first_user {
seen_first_user = true;
continue;
}
let text = msg.content_text();
if text.starts_with("Your context was just compacted")
|| text.starts_with("Your context was just rebuilt")
|| text.starts_with("[Earlier in this conversation")
|| text.starts_with("Here is the image")
|| text.contains("[image aged out")
{
continue;
}
if text.starts_with("[dmn]") {
target = StreamTarget::Autonomous;
let first_line = text.lines().next().unwrap_or("[dmn]");
let _ = ui_tx.send(UiMessage::DmnAnnotation(first_line.to_string()));
} else {
target = StreamTarget::Conversation;
let _ = ui_tx.send(UiMessage::UserInput(text.to_string()));
}
}
Role::Assistant => {
if let Some(ref calls) = msg.tool_calls {
for call in calls {
let _ = ui_tx.send(UiMessage::ToolCall {
name: call.function.name.clone(),
args_summary: String::new(),
});
}
}
let text = msg.content_text();
if !text.is_empty() {
let _ = ui_tx.send(UiMessage::TextDelta(format!("{}\n", text), target));
}
}
Role::Tool => {
let text = msg.content_text();
let preview: String = text.lines().take(3).collect::<Vec<_>>().join("\n");
let truncated = if text.lines().count() > 3 {
format!("{}...", preview)
} else {
preview
};
let _ = ui_tx.send(UiMessage::ToolResult {
name: String::new(),
result: truncated,
});
}
}
}
}