Fix chat display: restore incremental sync with change detection

sync_from_agent now detects changed entries by comparing token counts
(cheap proxy for content changes during streaming). Changed entries
get popped and re-pushed. Extracted push_routed/pop_routed helpers.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
Kent Overstreet 2026-04-08 16:55:18 -04:00
parent 31e813f57d
commit 5f5a8a807c

View file

@ -456,35 +456,7 @@ impl InteractScreen {
} }
} }
fn sync_from_agent(&mut self) { fn push_routed(&mut self, node: &AstNode) {
for _ in 0..self.pending_display_count {
self.conversation.pop_line();
}
self.pending_display_count = 0;
let (generation, entries) = {
let st = match self.agent.state.try_lock() {
Ok(st) => st,
Err(_) => return,
};
let generation = st.generation;
drop(st);
let ctx = match self.agent.context.try_lock() {
Ok(ctx) => ctx,
Err(_) => return,
};
(generation, ctx.conversation().to_vec())
};
if generation != self.last_generation || entries.len() < self.last_entries.len() {
self.conversation = PaneState::new(true);
self.autonomous = PaneState::new(true);
self.tools = PaneState::new(false);
self.last_entries.clear();
}
let start = self.last_entries.len();
for node in entries.iter().skip(start) {
for (target, text, marker) in Self::route_node(node) { for (target, text, marker) in Self::route_node(node) {
match target { match target {
PaneTarget::Conversation => { PaneTarget::Conversation => {
@ -508,6 +480,70 @@ impl InteractScreen {
} }
} }
} }
}
fn pop_routed(&mut self, node: &AstNode) {
for (target, _, _) in Self::route_node(node) {
match target {
PaneTarget::Conversation | PaneTarget::ConversationAssistant
=> self.conversation.pop_line(),
PaneTarget::Tools | PaneTarget::ToolResult
=> self.tools.pop_line(),
}
}
}
fn sync_from_agent(&mut self) {
for _ in 0..self.pending_display_count {
self.conversation.pop_line();
}
self.pending_display_count = 0;
let (generation, entries) = {
let st = match self.agent.state.try_lock() {
Ok(st) => st,
Err(_) => return,
};
let generation = st.generation;
drop(st);
let ctx = match self.agent.context.try_lock() {
Ok(ctx) => ctx,
Err(_) => return,
};
(generation, ctx.conversation().to_vec())
};
// Full reset on generation change
if generation != self.last_generation {
self.conversation = PaneState::new(true);
self.autonomous = PaneState::new(true);
self.tools = PaneState::new(false);
self.last_entries.clear();
}
// Detect changed entries (streaming updates mutate the last entry)
// Walk backwards from the end, pop any that differ
let mut pop_from = self.last_entries.len();
for i in (0..self.last_entries.len()).rev() {
if i >= entries.len() {
pop_from = i;
continue;
}
// Compare token count as a cheap change detector
if self.last_entries[i].tokens() != entries[i].tokens() {
pop_from = i;
} else {
break; // entries before this haven't changed
}
}
while self.last_entries.len() > pop_from {
let popped = self.last_entries.pop().unwrap();
self.pop_routed(&popped);
}
// Push new/changed entries
for node in entries.iter().skip(self.last_entries.len()) {
self.push_routed(node);
self.last_entries.push(node.clone()); self.last_entries.push(node.clone());
} }