merge poc-agent into poc-memory as agent/ module
Eliminates the circular dependency between poc-agent and poc-memory by moving all poc-agent source into poc-memory/src/agent/. The poc-agent binary now builds from poc-memory/src/bin/poc-agent.rs using library imports. All poc_agent:: references updated to crate::agent::. poc-agent/ directory kept for now (removed from workspace members). Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
parent
01abd795ce
commit
891cca57f8
35 changed files with 9178 additions and 88 deletions
157
poc-memory/src/agent/ui_channel.rs
Normal file
157
poc-memory/src/agent/ui_channel.rs
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
// 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, RwLock};
|
||||
use tokio::sync::{broadcast, mpsc};
|
||||
|
||||
/// Shared, live context state — agent writes, TUI reads for the debug screen.
|
||||
pub type SharedContextState = Arc<RwLock<Vec<ContextSection>>>;
|
||||
|
||||
/// Create a new shared context state.
|
||||
pub fn shared_context_state() -> SharedContextState {
|
||||
Arc::new(RwLock::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,
|
||||
}
|
||||
|
||||
/// A section of the context window, possibly with children.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ContextSection {
|
||||
pub name: String,
|
||||
pub tokens: usize,
|
||||
pub content: String,
|
||||
pub children: Vec<ContextSection>,
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
/// Streaming text delta — routed to conversation or autonomous pane
|
||||
/// based on the current StreamTarget.
|
||||
TextDelta(String, StreamTarget),
|
||||
|
||||
/// User's input echoed to conversation pane.
|
||||
UserInput(String),
|
||||
|
||||
/// Tool call header: [tool_name] with args summary.
|
||||
ToolCall {
|
||||
name: String,
|
||||
args_summary: String,
|
||||
},
|
||||
|
||||
/// Full tool result — goes to tools pane.
|
||||
ToolResult {
|
||||
name: String,
|
||||
result: String,
|
||||
},
|
||||
|
||||
/// DMN state annotation: [dmn: foraging (3/20)].
|
||||
DmnAnnotation(String),
|
||||
|
||||
/// Status bar update.
|
||||
StatusUpdate(StatusInfo),
|
||||
|
||||
/// Live activity indicator for the status bar — shows what the
|
||||
/// agent is doing right now ("thinking...", "calling: bash", etc).
|
||||
/// Empty string clears the indicator.
|
||||
Activity(String),
|
||||
|
||||
/// Reasoning/thinking tokens from the model (internal monologue).
|
||||
/// Routed to the autonomous pane so the user can peek at what
|
||||
/// the model is thinking about during long tool chains.
|
||||
Reasoning(String),
|
||||
|
||||
/// A tool call started — shown as a live overlay above the status bar.
|
||||
ToolStarted { id: String, name: String, detail: String },
|
||||
|
||||
/// A tool call finished — removes it from the live overlay.
|
||||
ToolFinished { id: String },
|
||||
|
||||
/// Debug message (only shown when POC_DEBUG is set).
|
||||
Debug(String),
|
||||
|
||||
/// Informational message — goes to conversation pane (command output, etc).
|
||||
Info(String),
|
||||
|
||||
/// Context loading details — stored for the debug screen (Ctrl+D).
|
||||
ContextInfoUpdate(ContextInfo),
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue