From 1b6664ee1cf26ce24756e29c16bfc6e978517dc9 Mon Sep 17 00:00:00 2001 From: Kent Overstreet Date: Wed, 8 Apr 2026 17:18:48 -0400 Subject: [PATCH] Fix: skip empty CoT nodes, expand AST children in conscious screen, timestamps Parser skips Thinking nodes that are just whitespace. Conscious screen now shows assistant children (Content, Thinking, ToolCall) as nested tree items via recursive node_to_view. Nodes get timestamped in push_node and on assistant branch creation. Co-Authored-By: Proof of Concept --- src/agent/context.rs | 4 +++- src/agent/mod.rs | 4 +++- src/user/widgets.rs | 38 ++++++++++++++++++++++---------------- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/agent/context.rs b/src/agent/context.rs index b393e5c..f560d0c 100644 --- a/src/agent/context.rs +++ b/src/agent/context.rs @@ -528,7 +528,9 @@ impl ResponseParser { self.buf = self.buf[end + 8..].to_string(); self.in_think = false; let text = std::mem::take(&mut self.think_buf); - self.push_child(ctx, AstNode::thinking(text)); + if !text.trim().is_empty() { + self.push_child(ctx, AstNode::thinking(text)); + } continue; } None => { diff --git a/src/agent/mod.rs b/src/agent/mod.rs index d647ac4..7ffdc87 100644 --- a/src/agent/mod.rs +++ b/src/agent/mod.rs @@ -263,6 +263,7 @@ impl Agent { } pub async fn push_node(&self, node: AstNode) { + let node = node.with_timestamp(chrono::Utc::now()); let st = self.state.lock().await; if let Some(ref log) = st.conversation_log { if let Err(e) = log.append_node(&node) { @@ -319,7 +320,8 @@ impl Agent { let mut ctx = agent.context.lock().await; let idx = ctx.len(Section::Conversation); ctx.push(Section::Conversation, - AstNode::branch(Role::Assistant, vec![])); + AstNode::branch(Role::Assistant, vec![]) + .with_timestamp(chrono::Utc::now())); idx }; diff --git a/src/user/widgets.rs b/src/user/widgets.rs index 88b1236..98f11fb 100644 --- a/src/user/widgets.rs +++ b/src/user/widgets.rs @@ -8,7 +8,7 @@ use ratatui::{ Frame, crossterm::event::KeyCode, }; -use crate::agent::context::{AstNode, NodeBody, Ast}; +use crate::agent::context::{AstNode, Ast}; #[derive(Debug, Clone)] pub struct SectionView { @@ -20,26 +20,32 @@ pub struct SectionView { pub status: String, } -pub fn section_to_view(name: &str, nodes: &[AstNode]) -> SectionView { - let children: Vec = nodes.iter().map(|node| { - let content = match node.leaf().map(|l| l.body()) { - Some(NodeBody::Log(_)) => String::new(), - Some(body) => body.text().to_string(), - None => node.children().iter() - .filter_map(|c| c.leaf()) - .filter(|l| matches!(l.body(), NodeBody::Content(_))) - .map(|l| l.body().text()) - .collect::>() - .join(""), - }; - SectionView { +fn node_to_view(node: &AstNode) -> SectionView { + match node { + AstNode::Leaf(leaf) => SectionView { name: node.label(), tokens: node.tokens(), - content, + content: leaf.body().text().to_string(), children: Vec::new(), status: String::new(), + }, + AstNode::Branch { children, .. } => { + let child_views: Vec = children.iter() + .map(|c| node_to_view(c)) + .collect(); + SectionView { + name: node.label(), + tokens: node.tokens(), + content: String::new(), + children: child_views, + status: String::new(), + } } - }).collect(); + } +} + +pub fn section_to_view(name: &str, nodes: &[AstNode]) -> SectionView { + let children: Vec = nodes.iter().map(|n| node_to_view(n)).collect(); let total_tokens: usize = nodes.iter().map(|n| n.tokens()).sum(); SectionView { name: name.to_string(),