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
|
|
@ -123,7 +123,6 @@ pub fn tools() -> Vec<Tool> {
|
||||||
read::tool(), write::tool(), edit::tool(),
|
read::tool(), write::tool(), edit::tool(),
|
||||||
grep::tool(), glob::tool(), bash::tool(),
|
grep::tool(), glob::tool(), bash::tool(),
|
||||||
vision::tool(),
|
vision::tool(),
|
||||||
working_stack::tool(),
|
|
||||||
];
|
];
|
||||||
all.extend(web::tools());
|
all.extend(web::tools());
|
||||||
all.extend(memory::memory_tools());
|
all.extend(memory::memory_tools());
|
||||||
|
|
|
||||||
|
|
@ -273,7 +273,7 @@ impl State {
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use crate::agent::{Agent, oneshot::{AutoAgent, AutoStep}};
|
use crate::agent::{Agent, oneshot::{AutoAgent, AutoStep}};
|
||||||
use crate::agent::context::ConversationEntry;
|
use crate::agent::context::{Ast, AstNode, NodeBody};
|
||||||
use crate::subconscious::defs;
|
use crate::subconscious::defs;
|
||||||
|
|
||||||
/// Names and byte-interval triggers for the built-in subconscious agents.
|
/// Names and byte-interval triggers for the built-in subconscious agents.
|
||||||
|
|
@ -472,22 +472,18 @@ impl Subconscious {
|
||||||
let rendered = store_guard.as_ref()
|
let rendered = store_guard.as_ref()
|
||||||
.and_then(|s| crate::cli::node::render_node(s, key));
|
.and_then(|s| crate::cli::node::render_node(s, key));
|
||||||
if let Some(rendered) = rendered {
|
if let Some(rendered) = rendered {
|
||||||
let mut msg = crate::agent::api::Message::user(format!(
|
ag.push_node(AstNode::memory(
|
||||||
"<system-reminder>\n--- {} (surfaced) ---\n{}\n</system-reminder>",
|
key,
|
||||||
key, rendered,
|
format!("--- {} (surfaced) ---\n{}", key, rendered),
|
||||||
));
|
));
|
||||||
msg.stamp();
|
|
||||||
ag.push_entry(ConversationEntry::Memory {
|
|
||||||
key: key.to_string(), message: msg, score: None,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(reflection) = outputs.get("reflection") {
|
if let Some(reflection) = outputs.get("reflection") {
|
||||||
if !reflection.trim().is_empty() {
|
if !reflection.trim().is_empty() {
|
||||||
ag.push_message(crate::agent::api::Message::user(format!(
|
ag.push_node(AstNode::dmn(format!(
|
||||||
"<system-reminder>\n--- subconscious reflection ---\n{}\n</system-reminder>",
|
"--- subconscious reflection ---\n{}",
|
||||||
reflection.trim(),
|
reflection.trim(),
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
@ -496,8 +492,8 @@ impl Subconscious {
|
||||||
if let Some(nudge) = outputs.get("thalamus") {
|
if let Some(nudge) = outputs.get("thalamus") {
|
||||||
let nudge = nudge.trim();
|
let nudge = nudge.trim();
|
||||||
if !nudge.is_empty() && nudge != "ok" {
|
if !nudge.is_empty() && nudge != "ok" {
|
||||||
ag.push_message(crate::agent::api::Message::user(format!(
|
ag.push_node(AstNode::dmn(format!(
|
||||||
"<system-reminder>\n--- thalamus ---\n{}\n</system-reminder>",
|
"--- thalamus ---\n{}",
|
||||||
nudge,
|
nudge,
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
@ -518,12 +514,13 @@ impl Subconscious {
|
||||||
pub async fn trigger(&mut self, agent: &Arc<tokio::sync::Mutex<Agent>>) {
|
pub async fn trigger(&mut self, agent: &Arc<tokio::sync::Mutex<Agent>>) {
|
||||||
let (conversation_bytes, memory_keys) = {
|
let (conversation_bytes, memory_keys) = {
|
||||||
let ag = agent.lock().await;
|
let ag = agent.lock().await;
|
||||||
let bytes = ag.context.conversation.entries().iter()
|
let bytes = ag.context.conversation().iter()
|
||||||
.filter(|ce| !ce.entry.is_log() && !ce.entry.is_memory())
|
.filter(|node| !matches!(node.leaf().map(|l| l.body()),
|
||||||
.map(|ce| ce.entry.message().content_text().len() as u64)
|
Some(NodeBody::Log(_)) | Some(NodeBody::Memory { .. })))
|
||||||
|
.map(|node| node.render().len() as u64)
|
||||||
.sum::<u64>();
|
.sum::<u64>();
|
||||||
let keys: Vec<String> = ag.context.conversation.entries().iter().filter_map(|ce| {
|
let keys: Vec<String> = ag.context.conversation().iter().filter_map(|node| {
|
||||||
if let ConversationEntry::Memory { key, .. } = &ce.entry {
|
if let Some(NodeBody::Memory { key, .. }) = node.leaf().map(|l| l.body()) {
|
||||||
Some(key.clone())
|
Some(key.clone())
|
||||||
} else { None }
|
} else { None }
|
||||||
}).collect();
|
}).collect();
|
||||||
|
|
@ -550,7 +547,7 @@ impl Subconscious {
|
||||||
|
|
||||||
let mut forked = conscious.fork(auto.tools.clone());
|
let mut forked = conscious.fork(auto.tools.clone());
|
||||||
forked.provenance = format!("agent:{}", auto.name);
|
forked.provenance = format!("agent:{}", auto.name);
|
||||||
let fork_point = forked.context.conversation.len();
|
let fork_point = forked.context.conversation().len();
|
||||||
let shared_forked = Arc::new(tokio::sync::Mutex::new(forked));
|
let shared_forked = Arc::new(tokio::sync::Mutex::new(forked));
|
||||||
|
|
||||||
self.agents[idx].forked_agent = Some(shared_forked.clone());
|
self.agents[idx].forked_agent = Some(shared_forked.clone());
|
||||||
|
|
|
||||||
|
|
@ -28,12 +28,9 @@ use crate::subconscious::learn;
|
||||||
|
|
||||||
pub use dmn::{SubconsciousSnapshot, Subconscious};
|
pub use dmn::{SubconsciousSnapshot, Subconscious};
|
||||||
|
|
||||||
use crate::agent::context::ConversationEntry;
|
use crate::agent::context::{AstNode, NodeBody, Section, Ast, ContextState};
|
||||||
|
|
||||||
/// Load persisted memory scores from disk and apply to Memory entries.
|
fn load_memory_scores(ctx: &mut ContextState, path: &std::path::Path) {
|
||||||
use crate::agent::context::ContextSection;
|
|
||||||
|
|
||||||
fn load_memory_scores(section: &mut ContextSection, path: &std::path::Path) {
|
|
||||||
let data = match std::fs::read_to_string(path) {
|
let data = match std::fs::read_to_string(path) {
|
||||||
Ok(d) => d,
|
Ok(d) => d,
|
||||||
Err(_) => return,
|
Err(_) => return,
|
||||||
|
|
@ -43,28 +40,31 @@ fn load_memory_scores(section: &mut ContextSection, path: &std::path::Path) {
|
||||||
Err(_) => return,
|
Err(_) => return,
|
||||||
};
|
};
|
||||||
let mut applied = 0;
|
let mut applied = 0;
|
||||||
for i in 0..section.len() {
|
for i in 0..ctx.conversation().len() {
|
||||||
if let ConversationEntry::Memory { key, .. } = §ion.entries()[i].entry {
|
if let AstNode::Leaf(leaf) = &ctx.conversation()[i] {
|
||||||
|
if let NodeBody::Memory { key, .. } = leaf.body() {
|
||||||
if let Some(&s) = scores.get(key.as_str()) {
|
if let Some(&s) = scores.get(key.as_str()) {
|
||||||
section.set_score(i, Some(s));
|
ctx.set_score(Section::Conversation, i, Some(s));
|
||||||
applied += 1;
|
applied += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if applied > 0 {
|
if applied > 0 {
|
||||||
dbglog!("[scoring] loaded {} scores from {}", applied, path.display());
|
dbglog!("[scoring] loaded {} scores from {}", applied, path.display());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Collect scored memory keys from conversation entries.
|
/// Collect scored memory keys from conversation entries.
|
||||||
fn collect_memory_scores(section: &ContextSection) -> std::collections::BTreeMap<String, f64> {
|
fn collect_memory_scores(ctx: &ContextState) -> std::collections::BTreeMap<String, f64> {
|
||||||
section.entries().iter()
|
ctx.conversation().iter()
|
||||||
.filter_map(|ce| {
|
.filter_map(|node| {
|
||||||
if let ConversationEntry::Memory { key, score: Some(s), .. } = &ce.entry {
|
if let AstNode::Leaf(leaf) = node {
|
||||||
Some((key.clone(), *s))
|
if let NodeBody::Memory { key, score: Some(s), .. } = leaf.body() {
|
||||||
} else {
|
return Some((key.clone(), *s));
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
@ -319,7 +319,7 @@ impl Mind {
|
||||||
|
|
||||||
// Restore persisted memory scores
|
// Restore persisted memory scores
|
||||||
let scores_path = self.config.session_dir.join("memory-scores.json");
|
let scores_path = self.config.session_dir.join("memory-scores.json");
|
||||||
load_memory_scores(&mut ag.context.conversation, &scores_path);
|
load_memory_scores(&mut ag.context, &scores_path);
|
||||||
|
|
||||||
ag.changed.notify_one();
|
ag.changed.notify_one();
|
||||||
drop(ag);
|
drop(ag);
|
||||||
|
|
@ -341,7 +341,7 @@ impl Mind {
|
||||||
MindCommand::Compact => {
|
MindCommand::Compact => {
|
||||||
let threshold = compaction_threshold(&self.config.app) as usize;
|
let threshold = compaction_threshold(&self.config.app) as usize;
|
||||||
let mut ag = self.agent.lock().await;
|
let mut ag = self.agent.lock().await;
|
||||||
if ag.context.total_tokens() > threshold {
|
if ag.context.tokens() > threshold {
|
||||||
ag.compact();
|
ag.compact();
|
||||||
ag.notify("compacted");
|
ag.notify("compacted");
|
||||||
}
|
}
|
||||||
|
|
@ -408,16 +408,17 @@ impl Mind {
|
||||||
async move {
|
async move {
|
||||||
let scores_snapshot = {
|
let scores_snapshot = {
|
||||||
let mut ag = agent.lock().await;
|
let mut ag = agent.lock().await;
|
||||||
for i in 0..ag.context.conversation.len() {
|
for i in 0..ag.context.conversation().len() {
|
||||||
if let ConversationEntry::Memory { key: k, .. } = &ag.context.conversation.entries()[i].entry {
|
if let AstNode::Leaf(leaf) = &ag.context.conversation()[i] {
|
||||||
|
if let NodeBody::Memory { key: k, .. } = leaf.body() {
|
||||||
if *k == key {
|
if *k == key {
|
||||||
ag.context.conversation.set_score(i, Some(score));
|
ag.context.set_score(Section::Conversation, i, Some(score));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ag.changed.notify_one();
|
ag.changed.notify_one();
|
||||||
// Snapshot scores while we have the lock
|
collect_memory_scores(&ag.context)
|
||||||
collect_memory_scores(&ag.context.conversation)
|
|
||||||
};
|
};
|
||||||
// Write to disk after releasing the lock
|
// Write to disk after releasing the lock
|
||||||
save_memory_scores(&scores_snapshot, &path);
|
save_memory_scores(&scores_snapshot, &path);
|
||||||
|
|
@ -437,18 +438,16 @@ impl Mind {
|
||||||
let mut ag = self.agent.lock().await;
|
let mut ag = self.agent.lock().await;
|
||||||
match target {
|
match target {
|
||||||
StreamTarget::Conversation => {
|
StreamTarget::Conversation => {
|
||||||
ag.push_message(crate::agent::api::Message::user(text));
|
ag.push_node(AstNode::user_msg(text));
|
||||||
}
|
}
|
||||||
StreamTarget::Autonomous => {
|
StreamTarget::Autonomous => {
|
||||||
let mut msg = crate::agent::api::Message::user(text);
|
ag.push_node(AstNode::dmn(text));
|
||||||
msg.stamp();
|
|
||||||
ag.push_entry(crate::agent::context::ConversationEntry::Dmn(msg));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compact if over budget before sending
|
// Compact if over budget before sending
|
||||||
let threshold = compaction_threshold(&self.config.app) as usize;
|
let threshold = compaction_threshold(&self.config.app) as usize;
|
||||||
if ag.context.total_tokens() > threshold {
|
if ag.context.tokens() > threshold {
|
||||||
ag.compact();
|
ag.compact();
|
||||||
ag.notify("compacted");
|
ag.notify("compacted");
|
||||||
}
|
}
|
||||||
|
|
@ -508,12 +507,6 @@ impl Mind {
|
||||||
crate::user::chat::cmd_switch_model(&self.agent, &name).await;
|
crate::user::chat::cmd_switch_model(&self.agent, &name).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Post-turn maintenance
|
|
||||||
{
|
|
||||||
let mut ag = self.agent.lock().await;
|
|
||||||
ag.age_out_images();
|
|
||||||
}
|
|
||||||
|
|
||||||
cmds.push(MindCommand::Compact);
|
cmds.push(MindCommand::Compact);
|
||||||
if !self.config.no_agents {
|
if !self.config.no_agents {
|
||||||
cmds.push(MindCommand::Score);
|
cmds.push(MindCommand::Score);
|
||||||
|
|
|
||||||
|
|
@ -40,14 +40,14 @@ fn build_messages(
|
||||||
filter: Filter,
|
filter: Filter,
|
||||||
) -> Vec<serde_json::Value> {
|
) -> Vec<serde_json::Value> {
|
||||||
let mut msgs = Vec::new();
|
let mut msgs = Vec::new();
|
||||||
for e in context.system.entries() {
|
for e in context.system().entries() {
|
||||||
msgs.push(serde_json::json!({"role": "system", "content": e.entry.message().content_text()}));
|
msgs.push(serde_json::json!({"role": "system", "content": e.entry.message().content_text()}));
|
||||||
}
|
}
|
||||||
let ctx = context.render_context_message();
|
let ctx = context.render_context_message();
|
||||||
if !ctx.is_empty() {
|
if !ctx.is_empty() {
|
||||||
msgs.push(serde_json::json!({"role": "user", "content": ctx}));
|
msgs.push(serde_json::json!({"role": "user", "content": ctx}));
|
||||||
}
|
}
|
||||||
let entries = context.conversation.entries();
|
let entries = context.conversation().entries();
|
||||||
for i in range {
|
for i in range {
|
||||||
let ce = &entries[i];
|
let ce = &entries[i];
|
||||||
let entry = &ce.entry;
|
let entry = &ce.entry;
|
||||||
|
|
|
||||||
121
src/user/chat.rs
121
src/user/chat.rs
|
|
@ -13,7 +13,7 @@ use ratatui::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{App, ScreenView, screen_legend};
|
use super::{App, ScreenView, screen_legend};
|
||||||
use crate::agent::api::Role;
|
use crate::agent::context::{AstNode, NodeBody, Role};
|
||||||
use crate::mind::MindCommand;
|
use crate::mind::MindCommand;
|
||||||
|
|
||||||
// --- Slash command table ---
|
// --- Slash command table ---
|
||||||
|
|
@ -376,7 +376,7 @@ pub(crate) struct InteractScreen {
|
||||||
call_timeout_secs: u64,
|
call_timeout_secs: u64,
|
||||||
// State sync with agent — double buffer
|
// State sync with agent — double buffer
|
||||||
last_generation: u64,
|
last_generation: u64,
|
||||||
last_entries: Vec<crate::agent::context::ContextEntry>,
|
last_entries: Vec<AstNode>,
|
||||||
pending_display_count: usize,
|
pending_display_count: usize,
|
||||||
/// Reference to agent for state sync
|
/// Reference to agent for state sync
|
||||||
agent: std::sync::Arc<tokio::sync::Mutex<crate::agent::Agent>>,
|
agent: std::sync::Arc<tokio::sync::Mutex<crate::agent::Agent>>,
|
||||||
|
|
@ -411,110 +411,79 @@ impl InteractScreen {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Route an agent entry to pane items.
|
fn route_node(node: &AstNode) -> Vec<(PaneTarget, String, Marker)> {
|
||||||
/// Returns empty vec for entries that shouldn't be displayed.
|
match node {
|
||||||
fn route_entry(entry: &crate::agent::context::ConversationEntry) -> Vec<(PaneTarget, String, Marker)> {
|
AstNode::Leaf(leaf) => {
|
||||||
use crate::agent::api::Role;
|
let text = leaf.body().text().to_string();
|
||||||
use crate::agent::context::ConversationEntry;
|
match leaf.body() {
|
||||||
|
NodeBody::Memory { .. } | NodeBody::Thinking(_)
|
||||||
match entry {
|
| NodeBody::Log(_) | NodeBody::Dmn(_) => vec![],
|
||||||
ConversationEntry::Memory { .. }
|
NodeBody::Content(_) => {
|
||||||
| ConversationEntry::Thinking(_)
|
if text.is_empty() || text.starts_with("<system-reminder>") { vec![] }
|
||||||
| ConversationEntry::Log(_) => return vec![],
|
else { vec![(PaneTarget::Conversation, text, Marker::User)] }
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
NodeBody::ToolCall { name, arguments } => {
|
||||||
let msg = entry.message();
|
let line = format!("[{}] {}", name, arguments.chars().take(80).collect::<String>());
|
||||||
let text = msg.content_text().to_string();
|
vec![(PaneTarget::Tools, line, Marker::None)]
|
||||||
|
|
||||||
if text.starts_with("<system-reminder>") {
|
|
||||||
return vec![];
|
|
||||||
}
|
}
|
||||||
|
NodeBody::ToolResult(t) => {
|
||||||
match msg.role {
|
if t.is_empty() { vec![] }
|
||||||
|
else { vec![(PaneTarget::ToolResult, text, Marker::None)] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AstNode::Branch { role, children } => {
|
||||||
|
match role {
|
||||||
Role::User => {
|
Role::User => {
|
||||||
if text.is_empty() { return vec![]; }
|
let text: String = children.iter()
|
||||||
vec![(PaneTarget::Conversation, text, Marker::User)]
|
.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 => {
|
Role::Assistant => {
|
||||||
let mut items = Vec::new();
|
let mut items = Vec::new();
|
||||||
// Tool calls → tools pane
|
for child in children {
|
||||||
if let Some(ref calls) = msg.tool_calls {
|
items.extend(Self::route_node(child));
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
// Re-tag content as assistant
|
||||||
|
for item in &mut items {
|
||||||
|
if item.0 == PaneTarget::Conversation {
|
||||||
|
item.0 = PaneTarget::ConversationAssistant;
|
||||||
|
item.2 = Marker::Assistant;
|
||||||
}
|
}
|
||||||
// Text content → conversation
|
|
||||||
if !text.is_empty() {
|
|
||||||
items.push((PaneTarget::ConversationAssistant, text, Marker::Assistant));
|
|
||||||
}
|
}
|
||||||
items
|
items
|
||||||
}
|
}
|
||||||
Role::Tool => {
|
|
||||||
if text.is_empty() { return vec![]; }
|
|
||||||
vec![(PaneTarget::ToolResult, text, Marker::None)]
|
|
||||||
}
|
|
||||||
Role::System => vec![],
|
Role::System => vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Sync conversation display from agent entries + pending input.
|
|
||||||
fn sync_from_agent(&mut self) {
|
fn sync_from_agent(&mut self) {
|
||||||
// Pop previously-displayed pending input
|
|
||||||
for _ in 0..self.pending_display_count {
|
for _ in 0..self.pending_display_count {
|
||||||
self.conversation.pop_line();
|
self.conversation.pop_line();
|
||||||
}
|
}
|
||||||
self.pending_display_count = 0;
|
self.pending_display_count = 0;
|
||||||
|
|
||||||
// Sync agent entries
|
|
||||||
if let Ok(agent) = self.agent.try_lock() {
|
if let Ok(agent) = self.agent.try_lock() {
|
||||||
let generation = agent.generation;
|
let generation = agent.generation;
|
||||||
let entries = agent.entries();
|
let entries = agent.conversation();
|
||||||
|
|
||||||
// Phase 1: detect desync and pop
|
if generation != self.last_generation || entries.len() < self.last_entries.len() {
|
||||||
if generation != self.last_generation {
|
|
||||||
self.conversation = PaneState::new(true);
|
self.conversation = PaneState::new(true);
|
||||||
self.autonomous = PaneState::new(true);
|
self.autonomous = PaneState::new(true);
|
||||||
self.tools = PaneState::new(false);
|
self.tools = PaneState::new(false);
|
||||||
self.last_entries.clear();
|
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();
|
let start = self.last_entries.len();
|
||||||
for entry in entries.iter().skip(start) {
|
for node in entries.iter().skip(start) {
|
||||||
for (target, text, marker) in Self::route_entry(&entry.entry) {
|
for (target, text, marker) in Self::route_node(node) {
|
||||||
match target {
|
match target {
|
||||||
PaneTarget::Conversation => {
|
PaneTarget::Conversation => {
|
||||||
self.conversation.current_color = Color::Cyan;
|
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;
|
self.last_generation = generation;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue