feat(v2): complete multi-LLM providers, TUI redesign, and advanced agent features
Multi-LLM Provider Support: - Add llm-core crate with LlmProvider trait abstraction - Implement Anthropic Claude API client with streaming - Implement OpenAI API client with streaming - Add token counting with SimpleTokenCounter and ClaudeTokenCounter - Add retry logic with exponential backoff and jitter Borderless TUI Redesign: - Rewrite theme system with terminal capability detection (Full/Unicode256/Basic) - Add provider tabs component with keybind switching [1]/[2]/[3] - Implement vim-modal input (Normal/Insert/Visual/Command modes) - Redesign chat panel with timestamps and streaming indicators - Add multi-provider status bar with cost tracking - Add Nerd Font icons with graceful ASCII fallbacks - Add syntax highlighting (syntect) and markdown rendering (pulldown-cmark) Advanced Agent Features: - Add system prompt builder with configurable components - Enhance subagent orchestration with parallel execution - Add git integration module for safe command detection - Add streaming tool results via channels - Expand tool set: AskUserQuestion, TodoWrite, LS, MultiEdit, BashOutput, KillShell - Add WebSearch with provider abstraction Plugin System Enhancement: - Add full agent definition parsing from YAML frontmatter - Add skill system with progressive disclosure - Wire plugin hooks into HookManager 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
12
crates/tools/todo/Cargo.toml
Normal file
12
crates/tools/todo/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "tools-todo"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
parking_lot = "0.12"
|
||||
color-eyre = "0.6"
|
||||
113
crates/tools/todo/src/lib.rs
Normal file
113
crates/tools/todo/src/lib.rs
Normal file
@@ -0,0 +1,113 @@
|
||||
//! TodoWrite tool for task list management
|
||||
|
||||
use parking_lot::RwLock;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Status of a todo item
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum TodoStatus {
|
||||
Pending,
|
||||
InProgress,
|
||||
Completed,
|
||||
}
|
||||
|
||||
/// A todo item
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Todo {
|
||||
pub content: String,
|
||||
pub status: TodoStatus,
|
||||
pub active_form: String, // Present continuous form for display
|
||||
}
|
||||
|
||||
/// Shared todo list state
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct TodoList {
|
||||
inner: Arc<RwLock<Vec<Todo>>>,
|
||||
}
|
||||
|
||||
impl TodoList {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Replace all todos with new list
|
||||
pub fn write(&self, todos: Vec<Todo>) {
|
||||
*self.inner.write() = todos;
|
||||
}
|
||||
|
||||
/// Get current todos
|
||||
pub fn read(&self) -> Vec<Todo> {
|
||||
self.inner.read().clone()
|
||||
}
|
||||
|
||||
/// Get the current in-progress task (for status display)
|
||||
pub fn current_task(&self) -> Option<String> {
|
||||
self.inner.read()
|
||||
.iter()
|
||||
.find(|t| t.status == TodoStatus::InProgress)
|
||||
.map(|t| t.active_form.clone())
|
||||
}
|
||||
|
||||
/// Get summary stats
|
||||
pub fn stats(&self) -> (usize, usize, usize) {
|
||||
let todos = self.inner.read();
|
||||
let pending = todos.iter().filter(|t| t.status == TodoStatus::Pending).count();
|
||||
let in_progress = todos.iter().filter(|t| t.status == TodoStatus::InProgress).count();
|
||||
let completed = todos.iter().filter(|t| t.status == TodoStatus::Completed).count();
|
||||
(pending, in_progress, completed)
|
||||
}
|
||||
|
||||
/// Format todos for display
|
||||
pub fn format_display(&self) -> String {
|
||||
let todos = self.inner.read();
|
||||
if todos.is_empty() {
|
||||
return "No tasks".to_string();
|
||||
}
|
||||
|
||||
todos.iter().enumerate().map(|(i, t)| {
|
||||
let status_icon = match t.status {
|
||||
TodoStatus::Pending => "○",
|
||||
TodoStatus::InProgress => "◐",
|
||||
TodoStatus::Completed => "●",
|
||||
};
|
||||
format!("{}. {} {}", i + 1, status_icon, t.content)
|
||||
}).collect::<Vec<_>>().join("\n")
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse todos from JSON tool input
|
||||
pub fn parse_todos(input: &serde_json::Value) -> color_eyre::Result<Vec<Todo>> {
|
||||
let todos = input.get("todos")
|
||||
.ok_or_else(|| color_eyre::eyre::eyre!("Missing 'todos' field"))?;
|
||||
|
||||
serde_json::from_value(todos.clone())
|
||||
.map_err(|e| color_eyre::eyre::eyre!("Invalid todos format: {}", e))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_todo_list() {
|
||||
let list = TodoList::new();
|
||||
|
||||
list.write(vec![
|
||||
Todo {
|
||||
content: "First task".to_string(),
|
||||
status: TodoStatus::Completed,
|
||||
active_form: "Completing first task".to_string(),
|
||||
},
|
||||
Todo {
|
||||
content: "Second task".to_string(),
|
||||
status: TodoStatus::InProgress,
|
||||
active_form: "Working on second task".to_string(),
|
||||
},
|
||||
]);
|
||||
|
||||
assert_eq!(list.current_task(), Some("Working on second task".to_string()));
|
||||
assert_eq!(list.stats(), (0, 1, 1));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user