Delete ui_channel.rs — relocate types, remove all UiMessage/UiSender plumbing

Types relocated:
- StreamTarget → mind/mod.rs (Mind decides Conversation vs Autonomous)
- SharedActiveTools + shared_active_tools() → agent/tools/mod.rs
- ContextSection + SharedContextState → agent/context.rs (already there)
- StatusInfo + ContextInfo → user/mod.rs (UI display state)

Removed UiSender from: Agent::turn, Mind, learn.rs, all function signatures.
The entire message-passing layer is gone. All state flows through
Agent fields (activities, entries, streaming) read by the UI via try_lock.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
ProofOfConcept 2026-04-05 22:34:48 -04:00
parent cfddb55ed9
commit f390fa1617
11 changed files with 72 additions and 165 deletions

View file

@ -16,7 +16,7 @@ use super::{
App, HotkeyAction, ScreenAction, ScreenView,
screen_legend,
};
use crate::user::ui_channel::StreamTarget;
use crate::mind::StreamTarget;
use crate::mind::MindCommand;
// --- Slash command table ---

View file

@ -13,6 +13,7 @@ use ratatui::{
};
use super::{App, ScreenAction, ScreenView, screen_legend};
use crate::agent::context::ContextSection;
pub(crate) struct ConsciousScreen {
scroll: u16,
@ -25,12 +26,12 @@ impl ConsciousScreen {
Self { scroll: 0, selected: None, expanded: std::collections::HashSet::new() }
}
fn read_context_state(&self, app: &App) -> Vec<crate::user::ui_channel::ContextSection> {
fn read_context_state(&self, app: &App) -> Vec<ContextSection> {
app.shared_context.read().map_or_else(|_| Vec::new(), |s| s.clone())
}
fn item_count(&self, context_state: &[crate::user::ui_channel::ContextSection]) -> usize {
fn count_section(section: &crate::user::ui_channel::ContextSection, expanded: &std::collections::HashSet<usize>, idx: &mut usize) -> usize {
fn item_count(&self, context_state: &[ContextSection]) -> usize {
fn count_section(section: &ContextSection, expanded: &std::collections::HashSet<usize>, idx: &mut usize) -> usize {
let my_idx = *idx;
*idx += 1;
let mut total = 1;
@ -63,7 +64,7 @@ impl ConsciousScreen {
fn render_section(
&self,
section: &crate::user::ui_channel::ContextSection,
section: &ContextSection,
depth: usize,
lines: &mut Vec<Line>,
idx: &mut usize,

View file

@ -8,7 +8,6 @@ pub mod context;
pub mod subconscious;
pub mod unconscious;
pub mod thalamus;
pub mod ui_channel;
use anyhow::Result;
use ratatui::crossterm::event::{Event, EventStream, KeyEventKind};
@ -30,7 +29,33 @@ use ratatui::{
};
use std::io;
use crate::user::ui_channel::{ContextInfo, SharedContextState, StatusInfo};
use crate::agent::context::SharedContextState;
/// Status info for the bottom status bar.
#[derive(Debug, Clone)]
pub struct StatusInfo {
pub dmn_state: String,
pub dmn_turns: u32,
pub dmn_max_turns: u32,
pub prompt_tokens: u32,
pub completion_tokens: u32,
pub model: String,
pub turn_tools: u32,
pub context_budget: String,
}
/// Context loading details for the debug screen.
#[derive(Debug, Clone)]
pub struct ContextInfo {
pub model: String,
pub available_models: Vec<String>,
pub prompt_file: String,
pub backend: String,
pub instruction_files: Vec<(String, usize)>,
pub memory_files: Vec<(String, usize)>,
pub system_prompt_chars: usize,
pub context_message_chars: usize,
}
/// Build the screen legend from screen labels.
pub(crate) fn screen_legend_from(interact: &dyn ScreenView, screens: &[Box<dyn ScreenView>]) -> String {
@ -100,7 +125,7 @@ pub struct App {
pub temperature: f32,
pub top_p: f32,
pub top_k: u32,
pub(crate) active_tools: crate::user::ui_channel::SharedActiveTools,
pub(crate) active_tools: crate::agent::tools::SharedActiveTools,
pub should_quit: bool,
pub submitted: Vec<String>,
pub hotkey_actions: Vec<HotkeyAction>,
@ -112,7 +137,7 @@ pub struct App {
}
impl App {
pub fn new(model: String, shared_context: SharedContextState, active_tools: crate::user::ui_channel::SharedActiveTools) -> Self {
pub fn new(model: String, shared_context: SharedContextState, active_tools: crate::agent::tools::SharedActiveTools) -> Self {
Self {
status: StatusInfo {
dmn_state: "resting".into(), dmn_turns: 0, dmn_max_turns: 20,
@ -189,11 +214,10 @@ pub async fn start(cli: crate::user::CliArgs) -> Result<()> {
unsafe { std::env::set_var("POC_DEBUG", "1") };
}
let (ui_tx, ui_rx) = ui_channel::channel();
let (turn_tx, turn_rx) = tokio::sync::mpsc::channel(1);
let (mind_tx, mind_rx) = tokio::sync::mpsc::unbounded_channel();
let mind = crate::mind::Mind::new(config, ui_tx.clone(), turn_tx);
let mind = crate::mind::Mind::new(config, turn_tx);
let shared_context = mind.agent.lock().await.shared_context.clone();
let shared_active_tools = mind.agent.lock().await.active_tools.clone();

View file

@ -1,107 +0,0 @@
// ui_channel.rs — Output routing for TUI panes
//
// All output from the agent (streaming text, tool calls, status updates)
// goes through a UiMessage enum sent over an mpsc channel. The TUI
// receives these messages and routes them to the appropriate pane.
//
// This replaces direct stdout/stderr printing throughout the codebase.
// The agent and API client never touch the terminal directly — they
// just send messages that the TUI renders where appropriate.
//
// The channel also fans out to a broadcast channel so the observation
// socket (observe.rs) can subscribe without touching the main path.
use std::sync::Arc;
use tokio::sync::{broadcast, mpsc};
// Re-export context types that moved to agent::context
pub use crate::agent::context::{ContextSection, SharedContextState, shared_context_state};
// ActiveToolCall lives in agent::tools — re-export for TUI access
pub use crate::agent::tools::ActiveToolCall;
/// Shared active tool calls — agent spawns, TUI reads metadata / aborts.
pub type SharedActiveTools = Arc<std::sync::Mutex<Vec<ActiveToolCall>>>;
pub fn shared_active_tools() -> SharedActiveTools {
Arc::new(std::sync::Mutex::new(Vec::new()))
}
/// Which pane streaming text should go to.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StreamTarget {
/// User-initiated turn — text goes to conversation pane.
Conversation,
/// DMN-initiated turn — text goes to autonomous pane.
Autonomous,
}
/// Status info for the bottom status bar.
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct StatusInfo {
pub dmn_state: String,
pub dmn_turns: u32,
pub dmn_max_turns: u32,
pub prompt_tokens: u32,
pub completion_tokens: u32,
pub model: String,
/// Number of tool calls dispatched in the current turn.
pub turn_tools: u32,
/// Context window budget breakdown (e.g. "id:8% mem:25% jnl:30% conv:37%").
pub context_budget: String,
}
/// Context loading details for the debug screen.
#[derive(Debug, Clone)]
pub struct ContextInfo {
pub model: String,
pub available_models: Vec<String>,
pub prompt_file: String,
pub backend: String,
#[allow(dead_code)]
pub instruction_files: Vec<(String, usize)>,
#[allow(dead_code)]
pub memory_files: Vec<(String, usize)>,
pub system_prompt_chars: usize,
pub context_message_chars: usize,
}
/// Messages sent from agent/API to the TUI for rendering.
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub enum UiMessage {
/// Informational message — goes to conversation pane (command output, etc).
Info(String),
}
/// Sender that fans out to both the TUI (mpsc) and observers (broadcast).
#[derive(Clone)]
pub struct UiSender {
tui: mpsc::UnboundedSender<UiMessage>,
observe: broadcast::Sender<UiMessage>,
}
impl UiSender {
pub fn send(&self, msg: UiMessage) -> Result<(), mpsc::error::SendError<UiMessage>> {
// Broadcast to observers (ignore errors — no subscribers is fine)
let _ = self.observe.send(msg.clone());
self.tui.send(msg)
}
/// Subscribe to the broadcast side (for the observation socket).
pub fn subscribe(&self) -> broadcast::Receiver<UiMessage> {
self.observe.subscribe()
}
}
/// Convenience type for the receiving half.
pub type UiReceiver = mpsc::UnboundedReceiver<UiMessage>;
/// Create a new UI channel pair.
pub fn channel() -> (UiSender, UiReceiver) {
let (tui_tx, tui_rx) = mpsc::unbounded_channel();
let (observe_tx, _) = broadcast::channel(1024);
(UiSender { tui: tui_tx, observe: observe_tx }, tui_rx)
}