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:
2025-11-01 21:05:29 +01:00
parent 6022aeb2b0
commit 04a7085007
3 changed files with 214 additions and 6 deletions

View File

@@ -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![

View 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()
}
}