WIP: Fix mind/, dmn, UI layer — 35 errors remaining
mind/mod.rs and mind/dmn.rs fully migrated to AST types. user/context.rs, user/widgets.rs, user/chat.rs partially migrated. Killed working_stack tool, tokenize_conv_entry, context_old.rs. Remaining: learn.rs (22), oneshot.rs (5), subconscious.rs (3), chat.rs (3), widgets.rs (1), context.rs (1). Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
parent
bf3e2a9b73
commit
d0d876e067
5 changed files with 99 additions and 141 deletions
137
src/user/chat.rs
137
src/user/chat.rs
|
|
@ -13,7 +13,7 @@ use ratatui::{
|
|||
};
|
||||
|
||||
use super::{App, ScreenView, screen_legend};
|
||||
use crate::agent::api::Role;
|
||||
use crate::agent::context::{AstNode, NodeBody, Role};
|
||||
use crate::mind::MindCommand;
|
||||
|
||||
// --- Slash command table ---
|
||||
|
|
@ -376,7 +376,7 @@ pub(crate) struct InteractScreen {
|
|||
call_timeout_secs: u64,
|
||||
// State sync with agent — double buffer
|
||||
last_generation: u64,
|
||||
last_entries: Vec<crate::agent::context::ContextEntry>,
|
||||
last_entries: Vec<AstNode>,
|
||||
pending_display_count: usize,
|
||||
/// Reference to agent for state sync
|
||||
agent: std::sync::Arc<tokio::sync::Mutex<crate::agent::Agent>>,
|
||||
|
|
@ -411,110 +411,79 @@ impl InteractScreen {
|
|||
}
|
||||
}
|
||||
|
||||
/// Route an agent entry to pane items.
|
||||
/// Returns empty vec for entries that shouldn't be displayed.
|
||||
fn route_entry(entry: &crate::agent::context::ConversationEntry) -> Vec<(PaneTarget, String, Marker)> {
|
||||
use crate::agent::api::Role;
|
||||
use crate::agent::context::ConversationEntry;
|
||||
|
||||
match entry {
|
||||
ConversationEntry::Memory { .. }
|
||||
| ConversationEntry::Thinking(_)
|
||||
| ConversationEntry::Log(_) => return vec![],
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let msg = entry.message();
|
||||
let text = msg.content_text().to_string();
|
||||
|
||||
if text.starts_with("<system-reminder>") {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
match msg.role {
|
||||
Role::User => {
|
||||
if text.is_empty() { return vec![]; }
|
||||
vec![(PaneTarget::Conversation, text, Marker::User)]
|
||||
}
|
||||
Role::Assistant => {
|
||||
let mut items = Vec::new();
|
||||
// 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>());
|
||||
items.push((PaneTarget::Tools, line, Marker::None));
|
||||
fn route_node(node: &AstNode) -> Vec<(PaneTarget, String, Marker)> {
|
||||
match node {
|
||||
AstNode::Leaf(leaf) => {
|
||||
let text = leaf.body().text().to_string();
|
||||
match leaf.body() {
|
||||
NodeBody::Memory { .. } | NodeBody::Thinking(_)
|
||||
| NodeBody::Log(_) | NodeBody::Dmn(_) => vec![],
|
||||
NodeBody::Content(_) => {
|
||||
if text.is_empty() || text.starts_with("<system-reminder>") { vec![] }
|
||||
else { vec![(PaneTarget::Conversation, text, Marker::User)] }
|
||||
}
|
||||
NodeBody::ToolCall { name, arguments } => {
|
||||
let line = format!("[{}] {}", name, arguments.chars().take(80).collect::<String>());
|
||||
vec![(PaneTarget::Tools, line, Marker::None)]
|
||||
}
|
||||
NodeBody::ToolResult(t) => {
|
||||
if t.is_empty() { vec![] }
|
||||
else { vec![(PaneTarget::ToolResult, text, Marker::None)] }
|
||||
}
|
||||
}
|
||||
// Text content → conversation
|
||||
if !text.is_empty() {
|
||||
items.push((PaneTarget::ConversationAssistant, text, Marker::Assistant));
|
||||
}
|
||||
AstNode::Branch { role, children } => {
|
||||
match role {
|
||||
Role::User => {
|
||||
let text: String = children.iter()
|
||||
.filter_map(|c| c.leaf())
|
||||
.filter(|l| matches!(l.body(), NodeBody::Content(_)))
|
||||
.map(|l| l.body().text())
|
||||
.collect::<Vec<_>>()
|
||||
.join("");
|
||||
if text.is_empty() || text.starts_with("<system-reminder>") { vec![] }
|
||||
else { vec![(PaneTarget::Conversation, text, Marker::User)] }
|
||||
}
|
||||
Role::Assistant => {
|
||||
let mut items = Vec::new();
|
||||
for child in children {
|
||||
items.extend(Self::route_node(child));
|
||||
}
|
||||
// Re-tag content as assistant
|
||||
for item in &mut items {
|
||||
if item.0 == PaneTarget::Conversation {
|
||||
item.0 = PaneTarget::ConversationAssistant;
|
||||
item.2 = Marker::Assistant;
|
||||
}
|
||||
}
|
||||
items
|
||||
}
|
||||
Role::System => vec![],
|
||||
}
|
||||
items
|
||||
}
|
||||
Role::Tool => {
|
||||
if text.is_empty() { return vec![]; }
|
||||
vec![(PaneTarget::ToolResult, text, Marker::None)]
|
||||
}
|
||||
Role::System => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Sync conversation display from agent entries + pending input.
|
||||
fn sync_from_agent(&mut self) {
|
||||
// Pop previously-displayed pending input
|
||||
for _ in 0..self.pending_display_count {
|
||||
self.conversation.pop_line();
|
||||
}
|
||||
self.pending_display_count = 0;
|
||||
|
||||
// Sync agent entries
|
||||
if let Ok(agent) = self.agent.try_lock() {
|
||||
let generation = agent.generation;
|
||||
let entries = agent.entries();
|
||||
let entries = agent.conversation();
|
||||
|
||||
// Phase 1: detect desync and pop
|
||||
if generation != self.last_generation {
|
||||
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();
|
||||
} else {
|
||||
let mut pop = self.last_entries.len();
|
||||
|
||||
for i in (0..self.last_entries.len()).rev() {
|
||||
// Check if this entry is out of bounds or doesn't match
|
||||
let matches = i < entries.len() && self.last_entries[i].entry == entries[i].entry;
|
||||
|
||||
if !matches {
|
||||
pop = i;
|
||||
}
|
||||
|
||||
// Only stop at assistant if it matches - otherwise keep going
|
||||
if matches && !self.last_entries[i].token_ids.is_empty()
|
||||
&& self.last_entries[i].entry.message().role == Role::Assistant {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while self.last_entries.len() > pop {
|
||||
let popped = self.last_entries.pop().unwrap();
|
||||
for (target, _, _) in Self::route_entry(&popped.entry) {
|
||||
match target {
|
||||
PaneTarget::Conversation | PaneTarget::ConversationAssistant
|
||||
=> self.conversation.pop_line(),
|
||||
PaneTarget::Tools | PaneTarget::ToolResult
|
||||
=> self.tools.pop_line(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 2: push new entries
|
||||
let start = self.last_entries.len();
|
||||
for entry in entries.iter().skip(start) {
|
||||
for (target, text, marker) in Self::route_entry(&entry.entry) {
|
||||
for node in entries.iter().skip(start) {
|
||||
for (target, text, marker) in Self::route_node(node) {
|
||||
match target {
|
||||
PaneTarget::Conversation => {
|
||||
self.conversation.current_color = Color::Cyan;
|
||||
|
|
@ -537,7 +506,7 @@ impl InteractScreen {
|
|||
}
|
||||
}
|
||||
}
|
||||
self.last_entries.push(entry.clone());
|
||||
self.last_entries.push(node.clone());
|
||||
}
|
||||
|
||||
self.last_generation = generation;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue