diff --git a/src/agent/mod.rs b/src/agent/mod.rs index f620af8..533190c 100644 --- a/src/agent/mod.rs +++ b/src/agent/mod.rs @@ -95,6 +95,8 @@ pub struct Agent { pub prompt_file: String, /// Stable session ID for memory-search dedup across turns. pub session_id: String, + /// Incremented on compaction — UI uses this to detect resets. + pub generation: u64, /// Agent orchestration state (surface-observe, journal, reflect). /// TODO: move to Session — it's session-level, not agent-level. pub agent_cycles: crate::subconscious::subconscious::AgentCycleState, @@ -153,6 +155,7 @@ impl Agent { app_config, prompt_file, session_id, + generation: 0, agent_cycles, active_tools, }; @@ -914,6 +917,7 @@ impl Agent { msg.content = Some(MessageContent::Text(replacement)); } } + self.generation += 1; } /// Strip ephemeral tool calls from the conversation history. @@ -959,6 +963,7 @@ impl Agent { dbglog!("[compact] budget: {}", budget.status_string()); self.load_startup_journal(); + self.generation += 1; self.last_prompt_tokens = 0; self.publish_context_state(); } diff --git a/src/user/chat.rs b/src/user/chat.rs index 5f0b0e8..d4939a3 100644 --- a/src/user/chat.rs +++ b/src/user/chat.rs @@ -31,10 +31,15 @@ pub(crate) struct InteractScreen { pub(crate) turn_started: Option, pub(crate) call_started: Option, pub(crate) call_timeout_secs: u64, + // State sync with agent + last_generation: u64, + last_entry_count: usize, + /// Reference to agent for state sync + agent: std::sync::Arc>, } impl InteractScreen { - pub fn new() -> Self { + pub fn new(agent: std::sync::Arc>) -> Self { Self { autonomous: PaneState::new(true), conversation: PaneState::new(true), @@ -48,9 +53,51 @@ impl InteractScreen { turn_started: None, call_started: None, call_timeout_secs: 60, + last_generation: 0, + last_entry_count: 0, + agent, } } + /// Sync conversation display from agent entries. + fn sync_from_agent(&mut self) { + let agent = self.agent.blocking_lock(); + let gen = agent.generation; + let count = agent.entries().len(); + + if gen != self.last_generation { + // Generation changed — full re-render + self.conversation = PaneState::new(true); + self.autonomous = PaneState::new(true); + self.tools = PaneState::new(false); + self.last_entry_count = 0; + } + + // Render new entries + if count > self.last_entry_count { + for entry in &agent.entries()[self.last_entry_count..] { + let msg = entry.message(); + let text = msg.content_text(); + match msg.role { + crate::agent::api::types::Role::User => { + self.conversation.push_line_with_marker( + text.to_string(), Color::Cyan, Marker::User, + ); + } + crate::agent::api::types::Role::Assistant => { + self.conversation.push_line_with_marker( + text.to_string(), Color::Reset, Marker::Assistant, + ); + } + _ => {} + } + } + } + + self.last_generation = gen; + self.last_entry_count = count; + } + /// Process a UiMessage — update pane state. pub fn handle_ui_message(&mut self, msg: &UiMessage, app: &mut App) { match msg { @@ -399,6 +446,9 @@ impl ScreenView for InteractScreen { } } + // Sync state from agent + self.sync_from_agent(); + // Draw self.draw_main(frame, area, app); None diff --git a/src/user/event_loop.rs b/src/user/event_loop.rs index 61c8217..c0dc01b 100644 --- a/src/user/event_loop.rs +++ b/src/user/event_loop.rs @@ -382,7 +382,7 @@ pub async fn run( let notify_rx = crate::thalamus::channels::subscribe_all(); // InteractScreen held separately for UiMessage routing - let mut interact = crate::user::chat::InteractScreen::new(); + let mut interact = crate::user::chat::InteractScreen::new(mind.agent.clone()); // Overlay screens: F2=conscious, F3=subconscious, F4=unconscious, F5=thalamus let mut screens: Vec> = vec![ Box::new(crate::user::context::ConsciousScreen::new()),