//! Per-session runtime state: message history, pending tool queue, //! background bash jobs, lesson/review counters, and the `TurnEvent` //! stream emitted while an agent turn is in flight. use std::path::PathBuf; use serde::{Deserialize, Serialize}; /// Cumulative token/latency counters for a session, persisted alongside it. #[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)] pub struct UsageStats { pub tokens_in: u64, pub tokens_out: u64, #[serde(default)] pub last_tokens_in: u64, #[serde(default)] pub last_tokens_out: u64, pub api_calls: u64, pub review_tokens: u64, pub total_ms: u64, } /// Mutable, serializable state for one session: chat history, tool /// results, pending tools, background jobs, and lesson/review counters /// shown in the TUI status bar. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SessionRuntime { pub messages: Vec, pub tool_call_results: Vec, pub pending_tool_queue: Vec, pub bash_jobs: Vec, pub subagent_queue: usize, pub edit_count: u32, pub consecutive_empty_reviews: u32, pub session_start: i64, pub lesson_count: u32, pub lessons_user: u32, pub lessons_feedback: u32, pub lessons_project: u32, pub lessons_reference: u32, pub lessons_active: u32, pub lessons_stale: u32, pub lessons_contradicted: u32, pub lessons_human: u32, pub lessons_verified: u32, pub lessons_unverified: u32, pub review_count: u32, pub session_dir: PathBuf, pub usage: UsageStats, /// Whether a hive-mind convergence has completed at least once in this /// session. Set by the main-thread event loop when it receives a /// `TurnEvent::SystemNote { kind: "hive_mind_converged", .. }` — the /// only reliable way to detect this across turns, since system messages /// pushed mid-turn inside `run_agent_turn` are NOT persisted into /// `rt.messages` (they stay local to that turn's background thread and /// are only archived to `SQLite`). pub hive_mind_converged: bool, } /// Record of one completed tool invocation, kept for transcript/history. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ToolCallResult { pub tool_call_id: String, pub tool_name: String, pub output: String, pub is_error: bool, pub duration_ms: u64, } /// A tool call awaiting execution, along with which execution model /// (inline, deferred, async) it should run under. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PendingTool { pub tool_name: String, pub args: serde_json::Value, pub execution_model: crate::app::state::types::ExecutionModel, } /// Reference to a background bash job tracked in session state (the actual /// process handle lives elsewhere; this is just the display/status record). #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BashJobRef { pub id: String, pub command: String, pub started_at: i64, pub running: bool, } /// Events emitted onto the turn-event queue while an agent turn runs, /// consumed by the event loop to update state and drive re-renders. #[derive(Debug, Clone)] pub enum TurnEvent { AssistantMessage(crate::dto::chat::message::ChatMessage), ToolResult { tool_call_id: String, tool_name: String, output: String, is_error: bool, path: Option, }, SystemNote { kind: String, message: String, }, StreamStart, StreamToken(String), StreamDone(crate::dto::chat::message::ChatMessage), Usage { tokens_in: u64, tokens_out: u64, }, Compacted(Vec), Error(String), Done, /// Real-time update from a workflow subagent: push the new status /// into `AppStateRest::workflow_engine.agents`. WorkflowAgentUpdate { agent_id: String, agent_name: String, status: crate::app::workflow::engine::AgentStatus, }, } impl SessionRuntime { /// Create fresh runtime state for a session rooted at `session_dir`, /// with all counters zeroed and `session_start` set to now. pub fn new(session_dir: PathBuf) -> Self { SessionRuntime { messages: Vec::new(), tool_call_results: Vec::new(), pending_tool_queue: Vec::new(), bash_jobs: Vec::new(), subagent_queue: 0, edit_count: 0, consecutive_empty_reviews: 0, session_start: chrono::Utc::now().timestamp_millis(), lesson_count: 0, lessons_user: 0, lessons_feedback: 0, lessons_project: 0, lessons_reference: 0, lessons_active: 0, lessons_stale: 0, lessons_contradicted: 0, lessons_human: 0, lessons_verified: 0, lessons_unverified: 0, review_count: 0, session_dir, usage: UsageStats::default(), hive_mind_converged: false, } } /// Append a message to the session's conversation history. pub fn push_message(&mut self, msg: crate::dto::chat::message::ChatMessage) { self.messages.push(msg); } }