feat: initial release — ZeroClaw v0.1.0
- 22 AI providers (OpenRouter, Anthropic, OpenAI, Mistral, etc.) - 7 channels (CLI, Telegram, Discord, Slack, iMessage, Matrix, Webhook) - 5-step onboarding wizard with Project Context personalization - OpenClaw-aligned system prompt (SOUL.md, IDENTITY.md, USER.md, AGENTS.md, etc.) - SQLite memory backend with auto-save - Skills system with on-demand loading - Security: autonomy levels, command allowlists, cost limits - 532 tests passing, 0 clippy warnings
This commit is contained in:
commit
05cb353f7f
71 changed files with 15757 additions and 0 deletions
189
src/tools/mod.rs
Normal file
189
src/tools/mod.rs
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
pub mod file_read;
|
||||
pub mod file_write;
|
||||
pub mod memory_forget;
|
||||
pub mod memory_recall;
|
||||
pub mod memory_store;
|
||||
pub mod shell;
|
||||
pub mod traits;
|
||||
|
||||
pub use file_read::FileReadTool;
|
||||
pub use file_write::FileWriteTool;
|
||||
pub use memory_forget::MemoryForgetTool;
|
||||
pub use memory_recall::MemoryRecallTool;
|
||||
pub use memory_store::MemoryStoreTool;
|
||||
pub use shell::ShellTool;
|
||||
pub use traits::Tool;
|
||||
#[allow(unused_imports)]
|
||||
pub use traits::{ToolResult, ToolSpec};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::memory::Memory;
|
||||
use crate::security::SecurityPolicy;
|
||||
use anyhow::Result;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Create the default tool registry
|
||||
pub fn default_tools(security: Arc<SecurityPolicy>) -> Vec<Box<dyn Tool>> {
|
||||
vec![
|
||||
Box::new(ShellTool::new(security.clone())),
|
||||
Box::new(FileReadTool::new(security.clone())),
|
||||
Box::new(FileWriteTool::new(security)),
|
||||
]
|
||||
}
|
||||
|
||||
/// Create full tool registry including memory tools
|
||||
pub fn all_tools(
|
||||
security: Arc<SecurityPolicy>,
|
||||
memory: Arc<dyn Memory>,
|
||||
) -> Vec<Box<dyn Tool>> {
|
||||
vec![
|
||||
Box::new(ShellTool::new(security.clone())),
|
||||
Box::new(FileReadTool::new(security.clone())),
|
||||
Box::new(FileWriteTool::new(security)),
|
||||
Box::new(MemoryStoreTool::new(memory.clone())),
|
||||
Box::new(MemoryRecallTool::new(memory.clone())),
|
||||
Box::new(MemoryForgetTool::new(memory)),
|
||||
]
|
||||
}
|
||||
|
||||
pub async fn handle_command(command: super::ToolCommands, config: Config) -> Result<()> {
|
||||
let security = Arc::new(SecurityPolicy {
|
||||
workspace_dir: config.workspace_dir.clone(),
|
||||
..SecurityPolicy::default()
|
||||
});
|
||||
let mem: Arc<dyn Memory> =
|
||||
Arc::from(crate::memory::create_memory(&config.memory, &config.workspace_dir)?);
|
||||
let tools_list = all_tools(security, mem);
|
||||
|
||||
match command {
|
||||
super::ToolCommands::List => {
|
||||
println!("Available tools ({}):", tools_list.len());
|
||||
for tool in &tools_list {
|
||||
println!(" - {}: {}", tool.name(), tool.description());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
super::ToolCommands::Test { tool, args } => {
|
||||
let matched = tools_list.iter().find(|t| t.name() == tool);
|
||||
match matched {
|
||||
Some(t) => {
|
||||
let parsed: serde_json::Value = serde_json::from_str(&args)?;
|
||||
let result = t.execute(parsed).await?;
|
||||
println!("Success: {}", result.success);
|
||||
println!("Output: {}", result.output);
|
||||
if let Some(err) = result.error {
|
||||
println!("Error: {err}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
None => anyhow::bail!("Unknown tool: {tool}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn default_tools_has_three() {
|
||||
let security = Arc::new(SecurityPolicy::default());
|
||||
let tools = default_tools(security);
|
||||
assert_eq!(tools.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_tools_names() {
|
||||
let security = Arc::new(SecurityPolicy::default());
|
||||
let tools = default_tools(security);
|
||||
let names: Vec<&str> = tools.iter().map(|t| t.name()).collect();
|
||||
assert!(names.contains(&"shell"));
|
||||
assert!(names.contains(&"file_read"));
|
||||
assert!(names.contains(&"file_write"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_tools_all_have_descriptions() {
|
||||
let security = Arc::new(SecurityPolicy::default());
|
||||
let tools = default_tools(security);
|
||||
for tool in &tools {
|
||||
assert!(
|
||||
!tool.description().is_empty(),
|
||||
"Tool {} has empty description",
|
||||
tool.name()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_tools_all_have_schemas() {
|
||||
let security = Arc::new(SecurityPolicy::default());
|
||||
let tools = default_tools(security);
|
||||
for tool in &tools {
|
||||
let schema = tool.parameters_schema();
|
||||
assert!(
|
||||
schema.is_object(),
|
||||
"Tool {} schema is not an object",
|
||||
tool.name()
|
||||
);
|
||||
assert!(
|
||||
schema["properties"].is_object(),
|
||||
"Tool {} schema has no properties",
|
||||
tool.name()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_spec_generation() {
|
||||
let security = Arc::new(SecurityPolicy::default());
|
||||
let tools = default_tools(security);
|
||||
for tool in &tools {
|
||||
let spec = tool.spec();
|
||||
assert_eq!(spec.name, tool.name());
|
||||
assert_eq!(spec.description, tool.description());
|
||||
assert!(spec.parameters.is_object());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_result_serde() {
|
||||
let result = ToolResult {
|
||||
success: true,
|
||||
output: "hello".into(),
|
||||
error: None,
|
||||
};
|
||||
let json = serde_json::to_string(&result).unwrap();
|
||||
let parsed: ToolResult = serde_json::from_str(&json).unwrap();
|
||||
assert!(parsed.success);
|
||||
assert_eq!(parsed.output, "hello");
|
||||
assert!(parsed.error.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_result_with_error_serde() {
|
||||
let result = ToolResult {
|
||||
success: false,
|
||||
output: String::new(),
|
||||
error: Some("boom".into()),
|
||||
};
|
||||
let json = serde_json::to_string(&result).unwrap();
|
||||
let parsed: ToolResult = serde_json::from_str(&json).unwrap();
|
||||
assert!(!parsed.success);
|
||||
assert_eq!(parsed.error.as_deref(), Some("boom"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_spec_serde() {
|
||||
let spec = ToolSpec {
|
||||
name: "test".into(),
|
||||
description: "A test tool".into(),
|
||||
parameters: serde_json::json!({"type": "object"}),
|
||||
};
|
||||
let json = serde_json::to_string(&spec).unwrap();
|
||||
let parsed: ToolSpec = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(parsed.name, "test");
|
||||
assert_eq!(parsed.description, "A test tool");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue