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>
155 lines
4.6 KiB
Rust
155 lines
4.6 KiB
Rust
// Integration test for plugin hooks with HookManager
|
|
use color_eyre::eyre::Result;
|
|
use hooks::{HookEvent, HookManager, HookResult};
|
|
use tempfile::TempDir;
|
|
|
|
#[tokio::test]
|
|
async fn test_register_and_execute_plugin_hooks() -> Result<()> {
|
|
// Create temporary directory to act as project root
|
|
let temp_dir = TempDir::new()?;
|
|
|
|
// Create hook manager
|
|
let mut hook_mgr = HookManager::new(temp_dir.path().to_str().unwrap());
|
|
|
|
// Register a hook that matches Edit|Write tools
|
|
hook_mgr.register_hook(
|
|
"PreToolUse".to_string(),
|
|
"echo 'Hook executed' && exit 0".to_string(),
|
|
Some("Edit|Write".to_string()),
|
|
Some(5000),
|
|
);
|
|
|
|
// Test that the hook executes for Edit tool
|
|
let event = HookEvent::PreToolUse {
|
|
tool: "Edit".to_string(),
|
|
args: serde_json::json!({"path": "/tmp/test.txt"}),
|
|
};
|
|
|
|
let result = hook_mgr.execute(&event, Some(5000)).await?;
|
|
assert_eq!(result, HookResult::Allow);
|
|
|
|
// Test that the hook executes for Write tool
|
|
let event = HookEvent::PreToolUse {
|
|
tool: "Write".to_string(),
|
|
args: serde_json::json!({"path": "/tmp/test.txt"}),
|
|
};
|
|
|
|
let result = hook_mgr.execute(&event, Some(5000)).await?;
|
|
assert_eq!(result, HookResult::Allow);
|
|
|
|
// Test that the hook does NOT execute for Read tool (doesn't match pattern)
|
|
let event = HookEvent::PreToolUse {
|
|
tool: "Read".to_string(),
|
|
args: serde_json::json!({"path": "/tmp/test.txt"}),
|
|
};
|
|
|
|
let result = hook_mgr.execute(&event, Some(5000)).await?;
|
|
assert_eq!(result, HookResult::Allow);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_deny_hook() -> Result<()> {
|
|
// Create temporary directory to act as project root
|
|
let temp_dir = TempDir::new()?;
|
|
|
|
// Create hook manager
|
|
let mut hook_mgr = HookManager::new(temp_dir.path().to_str().unwrap());
|
|
|
|
// Register a hook that denies Write operations
|
|
hook_mgr.register_hook(
|
|
"PreToolUse".to_string(),
|
|
"exit 2".to_string(), // Exit code 2 means deny
|
|
Some("Write".to_string()),
|
|
Some(5000),
|
|
);
|
|
|
|
// Test that the hook denies Write tool
|
|
let event = HookEvent::PreToolUse {
|
|
tool: "Write".to_string(),
|
|
args: serde_json::json!({"path": "/tmp/test.txt"}),
|
|
};
|
|
|
|
let result = hook_mgr.execute(&event, Some(5000)).await?;
|
|
assert_eq!(result, HookResult::Deny);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_multiple_hooks_same_event() -> Result<()> {
|
|
// Create temporary directory to act as project root
|
|
let temp_dir = TempDir::new()?;
|
|
|
|
// Create hook manager
|
|
let mut hook_mgr = HookManager::new(temp_dir.path().to_str().unwrap());
|
|
|
|
// Register multiple hooks for the same event
|
|
hook_mgr.register_hook(
|
|
"PreToolUse".to_string(),
|
|
"echo 'Hook 1' && exit 0".to_string(),
|
|
Some("Edit".to_string()),
|
|
Some(5000),
|
|
);
|
|
|
|
hook_mgr.register_hook(
|
|
"PreToolUse".to_string(),
|
|
"echo 'Hook 2' && exit 0".to_string(),
|
|
Some("Edit".to_string()),
|
|
Some(5000),
|
|
);
|
|
|
|
// Test that both hooks execute
|
|
let event = HookEvent::PreToolUse {
|
|
tool: "Edit".to_string(),
|
|
args: serde_json::json!({"path": "/tmp/test.txt"}),
|
|
};
|
|
|
|
let result = hook_mgr.execute(&event, Some(5000)).await?;
|
|
assert_eq!(result, HookResult::Allow);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_hook_with_no_pattern_matches_all() -> Result<()> {
|
|
// Create temporary directory to act as project root
|
|
let temp_dir = TempDir::new()?;
|
|
|
|
// Create hook manager
|
|
let mut hook_mgr = HookManager::new(temp_dir.path().to_str().unwrap());
|
|
|
|
// Register a hook with no pattern (matches all tools)
|
|
hook_mgr.register_hook(
|
|
"PreToolUse".to_string(),
|
|
"echo 'Hook for all tools' && exit 0".to_string(),
|
|
None, // No pattern = match all
|
|
Some(5000),
|
|
);
|
|
|
|
// Test that the hook executes for any tool
|
|
let event = HookEvent::PreToolUse {
|
|
tool: "Read".to_string(),
|
|
args: serde_json::json!({"path": "/tmp/test.txt"}),
|
|
};
|
|
let result = hook_mgr.execute(&event, Some(5000)).await?;
|
|
assert_eq!(result, HookResult::Allow);
|
|
|
|
let event = HookEvent::PreToolUse {
|
|
tool: "Write".to_string(),
|
|
args: serde_json::json!({"path": "/tmp/test.txt"}),
|
|
};
|
|
let result = hook_mgr.execute(&event, Some(5000)).await?;
|
|
assert_eq!(result, HookResult::Allow);
|
|
|
|
let event = HookEvent::PreToolUse {
|
|
tool: "Bash".to_string(),
|
|
args: serde_json::json!({"command": "ls"}),
|
|
};
|
|
let result = hook_mgr.execute(&event, Some(5000)).await?;
|
|
assert_eq!(result, HookResult::Allow);
|
|
|
|
Ok(())
|
|
}
|