diff --git a/src/user/api/mod.rs b/src/agent/api/mod.rs similarity index 99% rename from src/user/api/mod.rs rename to src/agent/api/mod.rs index 694ad9c..6c6554b 100644 --- a/src/user/api/mod.rs +++ b/src/agent/api/mod.rs @@ -6,15 +6,18 @@ // Diagnostics: anomalies always logged to debug panel. // Set POC_DEBUG=1 for verbose per-turn logging. +pub mod types; mod openai; +pub use types::*; + use anyhow::Result; use reqwest::Client; use std::time::{Duration, Instant}; use tokio::sync::mpsc; -use crate::user::types::*; +use crate::agent::tools::{ToolCall, ToolDef, FunctionCall}; use crate::user::ui_channel::{UiMessage, UiSender}; /// A JoinHandle that aborts its task when dropped. diff --git a/src/user/api/openai.rs b/src/agent/api/openai.rs similarity index 99% rename from src/user/api/openai.rs rename to src/agent/api/openai.rs index 4c73a7d..d780434 100644 --- a/src/user/api/openai.rs +++ b/src/agent/api/openai.rs @@ -8,7 +8,8 @@ use anyhow::Result; use reqwest::Client; use tokio::sync::mpsc; -use crate::user::types::*; +use crate::agent::tools::ToolDef; +use super::types::*; use crate::user::ui_channel::{UiMessage, UiSender}; use super::StreamEvent; diff --git a/src/user/types.rs b/src/agent/api/types.rs similarity index 54% rename from src/user/types.rs rename to src/agent/api/types.rs index 77cf257..6a1249c 100644 --- a/src/user/types.rs +++ b/src/agent/api/types.rs @@ -1,4 +1,4 @@ -// types.rs — OpenAI-compatible API types +// api/types.rs — OpenAI-compatible API types // // These mirror the OpenAI chat completion API, which is the de facto // standard that OpenRouter, vLLM, llama.cpp, and most inference @@ -9,11 +9,7 @@ use chrono::Utc; use serde::{Deserialize, Serialize}; -// Re-export tool types that moved to agent::tools -pub use crate::agent::tools::{ - ToolDef, ToolCall, ToolCallDelta, ToolOutput, - FunctionCall, FunctionDef, FunctionCallDelta, -}; +use crate::agent::tools::{ToolCall, ToolCallDelta, ToolDef, FunctionDef}; /// Message content — either plain text or an array of content parts /// (for multimodal messages with images). Serializes as a JSON string @@ -85,8 +81,6 @@ pub enum Role { Tool, } -// FunctionCall, FunctionDef moved to agent::tools - /// Chat completion request. #[derive(Debug, Serialize)] pub struct ChatRequest { @@ -180,8 +174,6 @@ pub struct Delta { pub tool_calls: Option>, } -// FunctionCallDelta moved to agent::tools - // --- Convenience constructors --- impl Message { @@ -278,184 +270,3 @@ impl Message { } } } - -impl ToolDef { - pub fn new(name: &str, description: &str, parameters: serde_json::Value) -> Self { - Self { - tool_type: "function".to_string(), - function: FunctionDef { - name: name.to_string(), - description: description.to_string(), - parameters, - }, - } - } -} - -/// Mutable context state — the structured regions of the context window. -/// Conversation entry — either a regular message or memory content. -/// Memory entries preserve the original message for KV cache round-tripping. -#[derive(Debug, Clone)] -pub enum ConversationEntry { - Message(Message), - Memory { key: String, message: Message }, -} - -// Custom serde: serialize Memory with a "memory_key" field added to the message, -// plain messages serialize as-is. This keeps the conversation log readable. -impl Serialize for ConversationEntry { - fn serialize(&self, s: S) -> Result { - use serde::ser::SerializeMap; - match self { - Self::Message(m) => m.serialize(s), - Self::Memory { key, message } => { - // Serialize message fields + memory_key - let json = serde_json::to_value(message).map_err(serde::ser::Error::custom)?; - let mut map = s.serialize_map(None)?; - if let serde_json::Value::Object(obj) = json { - for (k, v) in obj { - map.serialize_entry(&k, &v)?; - } - } - map.serialize_entry("memory_key", key)?; - map.end() - } - } - } -} - -impl<'de> Deserialize<'de> for ConversationEntry { - fn deserialize>(d: D) -> Result { - let mut json: serde_json::Value = serde_json::Value::deserialize(d)?; - if let Some(key) = json.as_object_mut().and_then(|o| o.remove("memory_key")) { - let key = key.as_str().unwrap_or("").to_string(); - let message: Message = serde_json::from_value(json).map_err(serde::de::Error::custom)?; - Ok(Self::Memory { key, message }) - } else { - let message: Message = serde_json::from_value(json).map_err(serde::de::Error::custom)?; - Ok(Self::Message(message)) - } - } -} - -impl ConversationEntry { - /// Get the API message for sending to the model. - pub fn api_message(&self) -> &Message { - match self { - Self::Message(m) => m, - Self::Memory { message, .. } => message, - } - } - - pub fn is_memory(&self) -> bool { - matches!(self, Self::Memory { .. }) - } - - /// Get a reference to the inner message. - pub fn message(&self) -> &Message { - match self { - Self::Message(m) => m, - Self::Memory { message, .. } => message, - } - } - - /// Get a mutable reference to the inner message. - pub fn message_mut(&mut self) -> &mut Message { - match self { - Self::Message(m) => m, - Self::Memory { message, .. } => message, - } - } -} - -#[derive(Clone)] -pub struct ContextState { - pub system_prompt: String, - pub personality: Vec<(String, String)>, - pub journal: Vec, - pub working_stack: Vec, - /// Conversation entries — messages and memory, interleaved in order. - /// Does NOT include system prompt, personality, or journal. - pub entries: Vec, -} - -// TODO: these should not be hardcoded absolute paths -pub fn working_stack_instructions_path() -> std::path::PathBuf { - dirs::home_dir().unwrap_or_default().join(".consciousness/config/working-stack.md") -} - -pub fn working_stack_file_path() -> std::path::PathBuf { - dirs::home_dir().unwrap_or_default().join(".consciousness/working-stack.json") -} - -impl ContextState { - /// Compute the context budget from typed sources. - pub fn budget(&self, count_str: &dyn Fn(&str) -> usize, - count_msg: &dyn Fn(&Message) -> usize, - window_tokens: usize) -> ContextBudget { - let id = count_str(&self.system_prompt) - + self.personality.iter().map(|(_, c)| count_str(c)).sum::(); - let jnl: usize = self.journal.iter().map(|e| count_str(&e.content)).sum(); - let mut mem = 0; - let mut conv = 0; - for entry in &self.entries { - let tokens = count_msg(entry.api_message()); - if entry.is_memory() { mem += tokens } else { conv += tokens } - } - ContextBudget { - identity_tokens: id, - memory_tokens: mem, - journal_tokens: jnl, - conversation_tokens: conv, - window_tokens, - } - } - - pub fn render_context_message(&self) -> String { - let mut parts: Vec = self.personality.iter() - .map(|(name, content)| format!("## {}\n\n{}", name, content)) - .collect(); - let instructions = std::fs::read_to_string(working_stack_instructions_path()).unwrap_or_default(); - let mut stack_section = instructions; - if self.working_stack.is_empty() { - stack_section.push_str("\n## Current stack\n\n(empty)\n"); - } else { - stack_section.push_str("\n## Current stack\n\n"); - for (i, item) in self.working_stack.iter().enumerate() { - if i == self.working_stack.len() - 1 { - stack_section.push_str(&format!("→ {}\n", item)); - } else { - stack_section.push_str(&format!(" [{}] {}\n", i, item)); - } - } - } - parts.push(stack_section); - parts.join("\n\n---\n\n") - } -} - -#[derive(Debug, Clone, Default)] -pub struct ContextBudget { - pub identity_tokens: usize, - pub memory_tokens: usize, - pub journal_tokens: usize, - pub conversation_tokens: usize, - pub window_tokens: usize, -} - -impl ContextBudget { - pub fn used(&self) -> usize { - self.identity_tokens + self.memory_tokens + self.journal_tokens + self.conversation_tokens - } - pub fn free(&self) -> usize { - self.window_tokens.saturating_sub(self.used()) - } - pub fn status_string(&self) -> String { - let total = self.window_tokens; - if total == 0 { return String::new(); } - let pct = |n: usize| if n == 0 { 0 } else { ((n * 100) / total).max(1) }; - format!("id:{}% mem:{}% jnl:{}% conv:{}% free:{}%", - pct(self.identity_tokens), pct(self.memory_tokens), - pct(self.journal_tokens), pct(self.conversation_tokens), pct(self.free())) - } -} diff --git a/src/agent/context.rs b/src/agent/context.rs index 4240029..ff283f8 100644 --- a/src/agent/context.rs +++ b/src/agent/context.rs @@ -4,10 +4,30 @@ // Journal entries are loaded from the memory graph store, not from // a flat file — the parse functions are gone. -use crate::user::types::*; +use std::sync::{Arc, RwLock}; + +use crate::agent::api::types::*; use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; use tiktoken_rs::CoreBPE; +/// 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, +} + +/// Shared, live context state — agent writes, TUI reads for the debug screen. +pub type SharedContextState = Arc>>; + +/// Create a new shared context state. +pub fn shared_context_state() -> SharedContextState { + Arc::new(RwLock::new(Vec::new())) +} + /// A single journal entry with its timestamp and content. #[derive(Debug, Clone)] pub struct JournalEntry { @@ -123,3 +143,171 @@ pub fn is_context_overflow(err: &anyhow::Error) -> bool { pub fn is_stream_error(err: &anyhow::Error) -> bool { err.to_string().contains("model stream error") } + +// --- Context state types --- + +/// Conversation entry — either a regular message or memory content. +/// Memory entries preserve the original message for KV cache round-tripping. +#[derive(Debug, Clone)] +pub enum ConversationEntry { + Message(Message), + Memory { key: String, message: Message }, +} + +// Custom serde: serialize Memory with a "memory_key" field added to the message, +// plain messages serialize as-is. This keeps the conversation log readable. +impl Serialize for ConversationEntry { + fn serialize(&self, s: S) -> Result { + use serde::ser::SerializeMap; + match self { + Self::Message(m) => m.serialize(s), + Self::Memory { key, message } => { + let json = serde_json::to_value(message).map_err(serde::ser::Error::custom)?; + let mut map = s.serialize_map(None)?; + if let serde_json::Value::Object(obj) = json { + for (k, v) in obj { + map.serialize_entry(&k, &v)?; + } + } + map.serialize_entry("memory_key", key)?; + map.end() + } + } + } +} + +impl<'de> Deserialize<'de> for ConversationEntry { + fn deserialize>(d: D) -> Result { + let mut json: serde_json::Value = serde_json::Value::deserialize(d)?; + if let Some(key) = json.as_object_mut().and_then(|o| o.remove("memory_key")) { + let key = key.as_str().unwrap_or("").to_string(); + let message: Message = serde_json::from_value(json).map_err(serde::de::Error::custom)?; + Ok(Self::Memory { key, message }) + } else { + let message: Message = serde_json::from_value(json).map_err(serde::de::Error::custom)?; + Ok(Self::Message(message)) + } + } +} + +impl ConversationEntry { + /// Get the API message for sending to the model. + pub fn api_message(&self) -> &Message { + match self { + Self::Message(m) => m, + Self::Memory { message, .. } => message, + } + } + + pub fn is_memory(&self) -> bool { + matches!(self, Self::Memory { .. }) + } + + /// Get a reference to the inner message. + pub fn message(&self) -> &Message { + match self { + Self::Message(m) => m, + Self::Memory { message, .. } => message, + } + } + + /// Get a mutable reference to the inner message. + pub fn message_mut(&mut self) -> &mut Message { + match self { + Self::Message(m) => m, + Self::Memory { message, .. } => message, + } + } +} + +#[derive(Clone)] +pub struct ContextState { + pub system_prompt: String, + pub personality: Vec<(String, String)>, + pub journal: Vec, + pub working_stack: Vec, + /// Conversation entries — messages and memory, interleaved in order. + /// Does NOT include system prompt, personality, or journal. + pub entries: Vec, +} + +// TODO: these should not be hardcoded absolute paths +pub fn working_stack_instructions_path() -> std::path::PathBuf { + dirs::home_dir().unwrap_or_default().join(".consciousness/config/working-stack.md") +} + +pub fn working_stack_file_path() -> std::path::PathBuf { + dirs::home_dir().unwrap_or_default().join(".consciousness/working-stack.json") +} + +impl ContextState { + /// Compute the context budget from typed sources. + pub fn budget(&self, count_str: &dyn Fn(&str) -> usize, + count_msg: &dyn Fn(&Message) -> usize, + window_tokens: usize) -> ContextBudget { + let id = count_str(&self.system_prompt) + + self.personality.iter().map(|(_, c)| count_str(c)).sum::(); + let jnl: usize = self.journal.iter().map(|e| count_str(&e.content)).sum(); + let mut mem = 0; + let mut conv = 0; + for entry in &self.entries { + let tokens = count_msg(entry.api_message()); + if entry.is_memory() { mem += tokens } else { conv += tokens } + } + ContextBudget { + identity_tokens: id, + memory_tokens: mem, + journal_tokens: jnl, + conversation_tokens: conv, + window_tokens, + } + } + + pub fn render_context_message(&self) -> String { + let mut parts: Vec = self.personality.iter() + .map(|(name, content)| format!("## {}\n\n{}", name, content)) + .collect(); + let instructions = std::fs::read_to_string(working_stack_instructions_path()).unwrap_or_default(); + let mut stack_section = instructions; + if self.working_stack.is_empty() { + stack_section.push_str("\n## Current stack\n\n(empty)\n"); + } else { + stack_section.push_str("\n## Current stack\n\n"); + for (i, item) in self.working_stack.iter().enumerate() { + if i == self.working_stack.len() - 1 { + stack_section.push_str(&format!("→ {}\n", item)); + } else { + stack_section.push_str(&format!(" [{}] {}\n", i, item)); + } + } + } + parts.push(stack_section); + parts.join("\n\n---\n\n") + } +} + +#[derive(Debug, Clone, Default)] +pub struct ContextBudget { + pub identity_tokens: usize, + pub memory_tokens: usize, + pub journal_tokens: usize, + pub conversation_tokens: usize, + pub window_tokens: usize, +} + +impl ContextBudget { + pub fn used(&self) -> usize { + self.identity_tokens + self.memory_tokens + self.journal_tokens + self.conversation_tokens + } + pub fn free(&self) -> usize { + self.window_tokens.saturating_sub(self.used()) + } + pub fn status_string(&self) -> String { + let total = self.window_tokens; + if total == 0 { return String::new(); } + let pct = |n: usize| if n == 0 { 0 } else { ((n * 100) / total).max(1) }; + format!("id:{}% mem:{}% jnl:{}% conv:{}% free:{}%", + pct(self.identity_tokens), pct(self.memory_tokens), + pct(self.journal_tokens), pct(self.conversation_tokens), pct(self.free())) + } +} diff --git a/src/agent/mod.rs b/src/agent/mod.rs index da8aecb..316f507 100644 --- a/src/agent/mod.rs +++ b/src/agent/mod.rs @@ -13,6 +13,7 @@ // to send here. This module just handles single turns: prompt // in, response out, tool calls dispatched. +pub mod api; pub mod context; pub mod tools; pub mod training; @@ -20,13 +21,17 @@ pub mod training; use anyhow::Result; use tiktoken_rs::CoreBPE; -use crate::user::api::ApiClient; -use crate::agent::context as journal; +use api::{ApiClient, StreamEvent}; +use context as journal; +use tools::{ToolCall, ToolDef, FunctionCall, summarize_args}; + use crate::user::log::ConversationLog; -use crate::user::api::StreamEvent; -use crate::agent::tools::{ToolCall, ToolDef, FunctionCall, summarize_args}; -use crate::user::types::*; -use crate::user::ui_channel::{ContextSection, SharedContextState, StatusInfo, StreamTarget, UiMessage, UiSender}; +use crate::agent::api::types::*; +use crate::agent::context::{ + ConversationEntry, ContextState, ContextBudget, + working_stack_instructions_path, working_stack_file_path, +}; +use crate::user::ui_channel::{ContextSection, SharedContextState, StreamTarget, StatusInfo, UiMessage, UiSender}; /// Result of a single agent turn. pub struct TurnResult { @@ -447,9 +452,7 @@ impl Agent { let _ = ui_tx.send(UiMessage::TextDelta("\n".to_string(), target)); } - let msg = crate::user::api::build_response_message(content, tool_calls); - - + let msg = api::build_response_message(content, tool_calls); if let Some(usage) = &usage { self.last_prompt_tokens = usage.prompt_tokens; diff --git a/src/agent/tools/control.rs b/src/agent/tools/control.rs index 6e3b4b9..8123ad3 100644 --- a/src/agent/tools/control.rs +++ b/src/agent/tools/control.rs @@ -7,7 +7,7 @@ use anyhow::{Context, Result}; use super::ToolOutput; -use crate::user::types::ToolDef; +use super::ToolDef; pub(super) fn pause(_args: &serde_json::Value) -> Result { Ok(ToolOutput { diff --git a/src/agent/tools/mod.rs b/src/agent/tools/mod.rs index 4ea4de8..050c6d1 100644 --- a/src/agent/tools/mod.rs +++ b/src/agent/tools/mod.rs @@ -5,13 +5,13 @@ // working_stack) and delegates everything else to thought::dispatch. // Core tools -pub mod bash; -pub mod edit; -pub mod glob; -pub mod grep; -pub mod memory; -pub mod read; -pub mod write; +mod bash; +mod edit; +mod glob; +mod grep; +mod memory; +mod read; +mod write; // Agent-specific tools mod control; @@ -53,6 +53,19 @@ pub struct ToolDef { pub function: FunctionDef, } +impl ToolDef { + pub fn new(name: &str, description: &str, parameters: serde_json::Value) -> Self { + Self { + tool_type: "function".to_string(), + function: FunctionDef { + name: name.to_string(), + description: description.to_string(), + parameters, + }, + } + } +} + /// A tool call requested by the model. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ToolCall { diff --git a/src/agent/tools/vision.rs b/src/agent/tools/vision.rs index 1f5d14f..03dc763 100644 --- a/src/agent/tools/vision.rs +++ b/src/agent/tools/vision.rs @@ -9,7 +9,7 @@ use base64::Engine; use serde::Deserialize; use super::ToolOutput; -use crate::user::types::ToolDef; +use super::ToolDef; #[derive(Deserialize)] struct Args { diff --git a/src/agent/tools/working_stack.rs b/src/agent/tools/working_stack.rs index abe9f39..323ad65 100644 --- a/src/agent/tools/working_stack.rs +++ b/src/agent/tools/working_stack.rs @@ -4,7 +4,7 @@ // internal tool — the agent uses it to maintain context across turns // and compaction. The model should never mention it to the user. -use crate::user::types::ToolDef; +use super::ToolDef; use serde_json::json; pub fn definition() -> ToolDef { diff --git a/src/agent/training.rs b/src/agent/training.rs index fb59848..9d029f5 100644 --- a/src/agent/training.rs +++ b/src/agent/training.rs @@ -8,8 +8,10 @@ // Column sums = response memory-dependence (training candidates) use std::time::Instant; -use crate::user::api::ApiClient; -use crate::user::types::*; + +use super::api::ApiClient; +use crate::agent::api::types::*; +use crate::agent::context::{ConversationEntry, ContextState}; use crate::user::ui_channel::{UiMessage, UiSender}; /// Timeout for individual /v1/score API calls. diff --git a/src/bin/consciousness.rs b/src/bin/consciousness.rs index d9c8b9a..f64b916 100644 --- a/src/bin/consciousness.rs +++ b/src/bin/consciousness.rs @@ -34,7 +34,9 @@ use poc_memory::dbglog; use poc_memory::user::*; use poc_memory::agent::{Agent, TurnResult}; -use poc_memory::user::api::ApiClient; +use poc_memory::agent::api::ApiClient; +use poc_memory::agent::api::types as api_types; +use poc_memory::agent::context as ctx; use poc_memory::user::tui::HotkeyAction; use poc_memory::config::{self, AppConfig, SessionConfig}; use poc_memory::user::ui_channel::{ContextInfo, StatusInfo, StreamTarget, UiMessage}; @@ -518,7 +520,7 @@ impl Session { let entries = agent_guard.entries_mut(); let mut last_user_text = None; while let Some(entry) = entries.last() { - if entry.message().role == poc_memory::user::types::Role::User { + if entry.message().role == api_types::Role::User { last_user_text = Some(entries.pop().unwrap().message().content_text().to_string()); break; @@ -1091,7 +1093,7 @@ fn drain_ui_messages(rx: &mut ui_channel::UiReceiver, app: &mut tui::App) -> boo /// conversation history immediately on restart. Shows user input, /// assistant responses, and brief tool call summaries. Skips the system /// prompt, context message, DMN plumbing, and image injection messages. -fn replay_session_to_ui(entries: &[types::ConversationEntry], ui_tx: &ui_channel::UiSender) { +fn replay_session_to_ui(entries: &[ctx::ConversationEntry], ui_tx: &ui_channel::UiSender) { use poc_memory::user::ui_channel::StreamTarget; dbglog!("[replay] replaying {} entries to UI", entries.len()); @@ -1111,8 +1113,8 @@ fn replay_session_to_ui(entries: &[types::ConversationEntry], ui_tx: &ui_channel if entry.is_memory() { continue; } let msg = entry.message(); match msg.role { - types::Role::System => {} - types::Role::User => { + api_types::Role::System => {} + api_types::Role::User => { // Skip context message (always the first user message) if !seen_first_user { seen_first_user = true; @@ -1140,7 +1142,7 @@ fn replay_session_to_ui(entries: &[types::ConversationEntry], ui_tx: &ui_channel let _ = ui_tx.send(UiMessage::UserInput(text.to_string())); } } - types::Role::Assistant => { + api_types::Role::Assistant => { if let Some(ref calls) = msg.tool_calls { for call in calls { let _ = ui_tx.send(UiMessage::ToolCall { @@ -1156,7 +1158,7 @@ fn replay_session_to_ui(entries: &[types::ConversationEntry], ui_tx: &ui_channel .send(UiMessage::TextDelta(format!("{}\n", text), target)); } } - types::Role::Tool => { + api_types::Role::Tool => { let text = msg.content_text(); let preview: String = text.lines().take(3).collect::>().join("\n"); diff --git a/src/subconscious/api.rs b/src/subconscious/api.rs index 82feb53..31cb3fc 100644 --- a/src/subconscious/api.rs +++ b/src/subconscious/api.rs @@ -7,9 +7,9 @@ // // Activated when config has api_base_url set. -use crate::user::api::ApiClient; -use crate::user::types::*; -use crate::agent::tools::{self as agent_tools}; +use crate::agent::api::ApiClient; +use crate::agent::api::types::*; +use crate::agent::tools::{self as agent_tools, ToolOutput}; use std::sync::OnceLock; diff --git a/src/user/log.rs b/src/user/log.rs index a2026ab..7dfc718 100644 --- a/src/user/log.rs +++ b/src/user/log.rs @@ -14,7 +14,7 @@ use std::fs::{File, OpenOptions}; use std::io::{BufRead, BufReader, Seek, SeekFrom, Write}; use std::path::{Path, PathBuf}; -use crate::user::types::ConversationEntry; +use crate::agent::context::ConversationEntry; pub struct ConversationLog { path: PathBuf, diff --git a/src/user/mod.rs b/src/user/mod.rs index 0d0f859..0d14a9e 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -9,8 +9,6 @@ // - cli, context, dmn, identity, log, observe, parsing, tui // Config moved to crate::config (unified with memory config) -pub mod api; -pub mod types; pub mod ui_channel; pub mod cli; pub mod dmn; diff --git a/src/user/parsing.rs b/src/user/parsing.rs index 587dd3b..74d9334 100644 --- a/src/user/parsing.rs +++ b/src/user/parsing.rs @@ -11,7 +11,8 @@ // Also handles streaming artifacts: whitespace inside XML tags from // token boundaries, tags, etc. -use crate::user::types::*; +use crate::agent::api::types::*; +use crate::agent::tools::{ToolCall, ToolDef, FunctionCall}; /// Parse leaked tool calls from response text. /// Looks for `...` blocks and tries both diff --git a/src/user/tui/context_screen.rs b/src/user/tui/context.rs similarity index 100% rename from src/user/tui/context_screen.rs rename to src/user/tui/context.rs diff --git a/src/user/tui/main_screen.rs b/src/user/tui/main.rs similarity index 100% rename from src/user/tui/main_screen.rs rename to src/user/tui/main.rs diff --git a/src/user/tui/mod.rs b/src/user/tui/mod.rs index b67b78b..82095a3 100644 --- a/src/user/tui/mod.rs +++ b/src/user/tui/mod.rs @@ -15,11 +15,11 @@ // subconscious_screen.rs — F3 subconscious (consolidation agents) // unconscious_screen.rs — F4 unconscious (memory daemon status) -mod main_screen; -mod context_screen; -mod subconscious_screen; -mod unconscious_screen; -mod thalamus_screen; +mod main; +mod context; +mod subconscious; +mod unconscious; +mod thalamus; pub(crate) const SCREEN_LEGEND: &str = " F1=interact F2=conscious F3=subconscious F4=unconscious F5=thalamus "; /// Subconscious agents — interact with conscious context diff --git a/src/user/tui/subconscious_screen.rs b/src/user/tui/subconscious.rs similarity index 100% rename from src/user/tui/subconscious_screen.rs rename to src/user/tui/subconscious.rs diff --git a/src/user/tui/thalamus_screen.rs b/src/user/tui/thalamus.rs similarity index 100% rename from src/user/tui/thalamus_screen.rs rename to src/user/tui/thalamus.rs diff --git a/src/user/tui/unconscious_screen.rs b/src/user/tui/unconscious.rs similarity index 100% rename from src/user/tui/unconscious_screen.rs rename to src/user/tui/unconscious.rs diff --git a/src/user/ui_channel.rs b/src/user/ui_channel.rs index d4d8bc0..5b614e3 100644 --- a/src/user/ui_channel.rs +++ b/src/user/ui_channel.rs @@ -11,16 +11,11 @@ // 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 std::sync::Arc; use tokio::sync::{broadcast, mpsc}; -/// Shared, live context state — agent writes, TUI reads for the debug screen. -pub type SharedContextState = Arc>>; - -/// Create a new shared context state. -pub fn shared_context_state() -> SharedContextState { - Arc::new(RwLock::new(Vec::new())) -} +// 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; @@ -57,15 +52,6 @@ pub struct StatusInfo { 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, -} - /// Context loading details for the debug screen. #[derive(Debug, Clone)] pub struct ContextInfo {