feat(repl): implement M12 REPL commands and session tracking
Add comprehensive REPL commands for session management and introspection: **Session Tracking** (`crates/core/agent/src/session.rs`): - SessionStats: Track messages, tool calls, tokens, timing - SessionHistory: Store conversation history and tool call records - Auto-formatting for durations (seconds, minutes, hours) **REPL Commands** (in interactive mode): - `/help` - List all available commands - `/status` - Show session stats (messages, tools, uptime) - `/permissions` - Display permission mode and tool access - `/cost` - Show token usage and timing (free with Ollama!) - `/history` - View conversation history - `/clear` - Reset session state - `/exit` - Exit interactive mode gracefully **Stats Tracking**: - Automatic message counting - Token estimation (chars / 4) - Duration tracking per message - Tool call counting (foundation for future) - Session uptime from start **Permission Display**: - Shows current mode (Plan/AcceptEdits/Code) - Lists tools by category (read-only, write, system) - Indicates which tools are allowed/ask/deny **UX Improvements**: - Welcome message shows model and mode - Clean command output with emoji indicators - Helpful error messages for unknown commands - Session stats persist across messages **Example Session**: ``` 🤖 Owlen Interactive Mode Model: qwen3:8b Mode: Plan > /help 📖 Available Commands: [list] > Find all Cargo.toml files 🔧 Tool call: glob... ✅ Tool result: 14 files > /status 📊 Session Status: Messages: 1 Tools: 1 calls Uptime: 15s > /cost 💰 Token Usage: ~234 tokens > /exit 👋 Goodbye! ``` Implements core M12 requirements for REPL commands and session management. Future: Checkpointing/rewind functionality can build on this foundation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,13 @@
|
||||
pub mod session;
|
||||
|
||||
use color_eyre::eyre::{Result, eyre};
|
||||
use futures_util::TryStreamExt;
|
||||
use llm_ollama::{ChatMessage, OllamaClient, OllamaOptions, Tool, ToolFunction, ToolParameters};
|
||||
use permissions::{PermissionDecision, PermissionManager, Tool as PermTool};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
pub use session::{SessionStats, SessionHistory, ToolCallRecord};
|
||||
|
||||
/// Define all available tools for the LLM
|
||||
pub fn get_tool_definitions() -> Vec<Tool> {
|
||||
vec![
|
||||
|
||||
99
crates/core/agent/src/session.rs
Normal file
99
crates/core/agent/src/session.rs
Normal file
@@ -0,0 +1,99 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SessionStats {
|
||||
pub start_time: SystemTime,
|
||||
pub total_messages: usize,
|
||||
pub total_tool_calls: usize,
|
||||
pub total_duration: Duration,
|
||||
pub estimated_tokens: usize,
|
||||
}
|
||||
|
||||
impl SessionStats {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
start_time: SystemTime::now(),
|
||||
total_messages: 0,
|
||||
total_tool_calls: 0,
|
||||
total_duration: Duration::ZERO,
|
||||
estimated_tokens: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn record_message(&mut self, tokens: usize, duration: Duration) {
|
||||
self.total_messages += 1;
|
||||
self.estimated_tokens += tokens;
|
||||
self.total_duration += duration;
|
||||
}
|
||||
|
||||
pub fn record_tool_call(&mut self) {
|
||||
self.total_tool_calls += 1;
|
||||
}
|
||||
|
||||
pub fn format_duration(d: Duration) -> String {
|
||||
let secs = d.as_secs();
|
||||
if secs < 60 {
|
||||
format!("{}s", secs)
|
||||
} else if secs < 3600 {
|
||||
format!("{}m {}s", secs / 60, secs % 60)
|
||||
} else {
|
||||
format!("{}h {}m", secs / 3600, (secs % 3600) / 60)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SessionStats {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SessionHistory {
|
||||
pub user_prompts: Vec<String>,
|
||||
pub assistant_responses: Vec<String>,
|
||||
pub tool_calls: Vec<ToolCallRecord>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ToolCallRecord {
|
||||
pub tool_name: String,
|
||||
pub arguments: String,
|
||||
pub result: String,
|
||||
pub success: bool,
|
||||
}
|
||||
|
||||
impl SessionHistory {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
user_prompts: Vec::new(),
|
||||
assistant_responses: Vec::new(),
|
||||
tool_calls: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_user_message(&mut self, message: String) {
|
||||
self.user_prompts.push(message);
|
||||
}
|
||||
|
||||
pub fn add_assistant_message(&mut self, message: String) {
|
||||
self.assistant_responses.push(message);
|
||||
}
|
||||
|
||||
pub fn add_tool_call(&mut self, record: ToolCallRecord) {
|
||||
self.tool_calls.push(record);
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.user_prompts.clear();
|
||||
self.assistant_responses.clear();
|
||||
self.tool_calls.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SessionHistory {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user