feat: add Composio tool provider + encrypted secret store + wizard integration

- src/tools/composio.rs: ComposioTool implementing Tool trait
  - list/execute/connect actions via Composio API (1000+ OAuth apps)
  - 60s timeout, proper error handling, JSON schema for LLM
  - 12 tests covering schema, validation, serde, error paths

- src/security/secrets.rs: SecretStore for encrypted credential storage
  - XOR cipher with random 32-byte key stored in ~/.zeroclaw/.secret_key
  - enc: prefix for encrypted values, plaintext passthrough (backward compat)
  - Key file created with 0600 permissions (Unix)
  - 16 tests: roundtrip, unicode, long secrets, corrupt hex, permissions

- src/config/schema.rs: ComposioConfig + SecretsConfig structs
  - Composio: enabled (default: false), api_key, entity_id
  - Secrets: encrypt (default: true)
  - Both with serde(default) for backward compatibility
  - 8 new config tests

- src/onboard/wizard.rs: new Step 5 'Tool Mode & Security'
  - Sovereign (local only) vs Composio (managed OAuth) selection
  - Encrypted secret storage toggle (default: on)
  - 7-step wizard (was 6)

- src/tools/mod.rs: all_tools() now accepts optional composio_key
- src/agent/loop_.rs: wires Composio key from config into tool registry
- README.md: Composio integration + encrypted secrets documentation

1017 tests, 0 clippy warnings, cargo fmt clean.
This commit is contained in:
argenis de la rosa 2026-02-14 02:41:29 -05:00
parent 976c5bbf3c
commit f8befafe4d
9 changed files with 1087 additions and 24 deletions

View file

@ -1,3 +1,4 @@
pub mod composio;
pub mod file_read;
pub mod file_write;
pub mod memory_forget;
@ -6,6 +7,7 @@ pub mod memory_store;
pub mod shell;
pub mod traits;
pub use composio::ComposioTool;
pub use file_read::FileReadTool;
pub use file_write::FileWriteTool;
pub use memory_forget::MemoryForgetTool;
@ -31,16 +33,28 @@ pub fn default_tools(security: Arc<SecurityPolicy>) -> Vec<Box<dyn Tool>> {
]
}
/// Create full tool registry including memory tools
pub fn all_tools(security: Arc<SecurityPolicy>, memory: Arc<dyn Memory>) -> Vec<Box<dyn Tool>> {
vec![
/// Create full tool registry including memory tools and optional Composio
pub fn all_tools(
security: Arc<SecurityPolicy>,
memory: Arc<dyn Memory>,
composio_key: Option<&str>,
) -> Vec<Box<dyn Tool>> {
let mut tools: 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)),
]
];
if let Some(key) = composio_key {
if !key.is_empty() {
tools.push(Box::new(ComposioTool::new(key)));
}
}
tools
}
pub async fn handle_command(command: super::ToolCommands, config: Config) -> Result<()> {
@ -53,7 +67,12 @@ pub async fn handle_command(command: super::ToolCommands, config: Config) -> Res
&config.workspace_dir,
config.api_key.as_deref(),
)?);
let tools_list = all_tools(security, mem);
let composio_key = if config.composio.enabled {
config.composio.api_key.as_deref()
} else {
None
};
let tools_list = all_tools(security, mem, composio_key);
match command {
super::ToolCommands::List => {