From 2c7021e90f0dbcedf9c9b6426e1bc15923e68ecb Mon Sep 17 00:00:00 2001 From: argenis de la rosa Date: Sat, 14 Feb 2026 15:50:53 -0500 Subject: [PATCH] fix: add memory config to wizard and fix clippy warnings - Add chunk_max_tokens field to MemoryConfig in quick setup - Add memory_backend parameter to run_quick_setup() - Add setup_memory() step to interactive wizard (8 steps now) - Fix clippy if_not_else warning - Fix clippy match_same_arms warning - Add clippy allows for browser.rs (too_many_lines, unnecessary_wraps) --- src/main.rs | 11 +++-- src/onboard/wizard.rs | 112 ++++++++++++++++++++++++++++++++++++------ src/tools/browser.rs | 19 ++++--- 3 files changed, 117 insertions(+), 25 deletions(-) diff --git a/src/main.rs b/src/main.rs index 34e5aed..4d07ad2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -81,6 +81,10 @@ enum Commands { /// Provider name (used in quick mode, default: openrouter) #[arg(long)] provider: Option, + + /// Memory backend (sqlite, markdown, none) - used in quick mode, default: sqlite + #[arg(long)] + memory: Option, }, /// Start the AI agent loop @@ -264,13 +268,14 @@ async fn main() -> Result<()> { channels_only, api_key, provider, + memory, } = &cli.command { if *interactive && *channels_only { bail!("Use either --interactive or --channels-only, not both"); } - if *channels_only && (api_key.is_some() || provider.is_some()) { - bail!("--channels-only does not accept --api-key or --provider"); + if *channels_only && (api_key.is_some() || provider.is_some() || memory.is_some()) { + bail!("--channels-only does not accept --api-key, --provider, or --memory"); } let config = if *channels_only { @@ -278,7 +283,7 @@ async fn main() -> Result<()> { } else if *interactive { onboard::run_wizard()? } else { - onboard::run_quick_setup(api_key.as_deref(), provider.as_deref())? + onboard::run_quick_setup(api_key.as_deref(), provider.as_deref(), memory.as_deref())? }; // Auto-start channels if user said yes during wizard if std::env::var("ZEROCLAW_AUTOSTART_CHANNELS").as_deref() == Ok("1") { diff --git a/src/onboard/wizard.rs b/src/onboard/wizard.rs index 6e48f2d..b5efc83 100644 --- a/src/onboard/wizard.rs +++ b/src/onboard/wizard.rs @@ -55,25 +55,28 @@ pub fn run_wizard() -> Result { ); println!(); - print_step(1, 7, "Workspace Setup"); + print_step(1, 8, "Workspace Setup"); let (workspace_dir, config_path) = setup_workspace()?; - print_step(2, 7, "AI Provider & API Key"); + print_step(2, 8, "AI Provider & API Key"); let (provider, api_key, model) = setup_provider()?; - print_step(3, 7, "Channels (How You Talk to ZeroClaw)"); + print_step(3, 8, "Channels (How You Talk to ZeroClaw)"); let channels_config = setup_channels()?; - print_step(4, 7, "Tunnel (Expose to Internet)"); + print_step(4, 8, "Tunnel (Expose to Internet)"); let tunnel_config = setup_tunnel()?; - print_step(5, 7, "Tool Mode & Security"); + print_step(5, 8, "Tool Mode & Security"); let (composio_config, secrets_config) = setup_tool_mode()?; - print_step(6, 7, "Project Context (Personalize Your Agent)"); + print_step(6, 8, "Memory Configuration"); + let memory_config = setup_memory()?; + + print_step(7, 8, "Project Context (Personalize Your Agent)"); let project_ctx = setup_project_context()?; - print_step(7, 7, "Workspace Files"); + print_step(8, 8, "Workspace Files"); scaffold_workspace(&workspace_dir, &project_ctx)?; // ── Build config ── @@ -95,7 +98,7 @@ pub fn run_wizard() -> Result { reliability: crate::config::ReliabilityConfig::default(), heartbeat: HeartbeatConfig::default(), channels_config, - memory: MemoryConfig::default(), // SQLite + auto-save by default + memory: memory_config, // User-selected memory backend tunnel: tunnel_config, gateway: crate::config::GatewayConfig::default(), composio: composio_config, @@ -110,9 +113,10 @@ pub fn run_wizard() -> Result { style("Supervised").green() ); println!( - " {} Memory: {} (auto-save: on)", + " {} Memory: {} (auto-save: {})", style("✓").green().bold(), - style("sqlite").green() + style(&config.memory.backend).green(), + if config.memory.auto_save { "on" } else { "off" } ); config.save()?; @@ -210,10 +214,10 @@ pub fn run_channels_repair_wizard() -> Result { // ── Quick setup (zero prompts) ─────────────────────────────────── /// Non-interactive setup: generates a sensible default config instantly. -/// Use `zeroclaw onboard` or `zeroclaw onboard --api-key sk-... --provider openrouter`. +/// Use `zeroclaw onboard` or `zeroclaw onboard --api-key sk-... --provider openrouter --memory sqlite`. /// Use `zeroclaw onboard --interactive` for the full wizard. #[allow(clippy::too_many_lines)] -pub fn run_quick_setup(api_key: Option<&str>, provider: Option<&str>) -> Result { +pub fn run_quick_setup(api_key: Option<&str>, provider: Option<&str>, memory_backend: Option<&str>) -> Result { println!("{}", style(BANNER).cyan().bold()); println!( " {}", @@ -234,6 +238,24 @@ pub fn run_quick_setup(api_key: Option<&str>, provider: Option<&str>) -> Result< let provider_name = provider.unwrap_or("openrouter").to_string(); let model = default_model_for_provider(&provider_name); + let memory_backend_name = memory_backend.unwrap_or("sqlite").to_string(); + + // Create memory config based on backend choice + let memory_config = MemoryConfig { + backend: memory_backend_name.clone(), + auto_save: memory_backend_name != "none", + hygiene_enabled: memory_backend_name == "sqlite", + archive_after_days: if memory_backend_name == "sqlite" { 7 } else { 0 }, + purge_after_days: if memory_backend_name == "sqlite" { 30 } else { 0 }, + conversation_retention_days: 30, + embedding_provider: "none".to_string(), + embedding_model: "text-embedding-3-small".to_string(), + embedding_dimensions: 1536, + vector_weight: 0.7, + keyword_weight: 0.3, + embedding_cache_size: if memory_backend_name == "sqlite" { 10000 } else { 0 }, + chunk_max_tokens: 512, + }; let config = Config { workspace_dir: workspace_dir.clone(), @@ -248,7 +270,7 @@ pub fn run_quick_setup(api_key: Option<&str>, provider: Option<&str>) -> Result< reliability: crate::config::ReliabilityConfig::default(), heartbeat: HeartbeatConfig::default(), channels_config: ChannelsConfig::default(), - memory: MemoryConfig::default(), + memory: memory_config, tunnel: crate::config::TunnelConfig::default(), gateway: crate::config::GatewayConfig::default(), composio: ComposioConfig::default(), @@ -300,9 +322,10 @@ pub fn run_quick_setup(api_key: Option<&str>, provider: Option<&str>) -> Result< style("Supervised (workspace-scoped)").green() ); println!( - " {} Memory: {}", + " {} Memory: {} (auto-save: {})", style("✓").green().bold(), - style("sqlite (auto-save)").green() + style(&memory_backend_name).green(), + if memory_backend_name == "none" { "off" } else { "on" } ); println!( " {} Secrets: {}", @@ -932,6 +955,65 @@ fn setup_project_context() -> Result { }) } +// ── Step 6: Memory Configuration ─────────────────────────────── + +fn setup_memory() -> Result { + print_bullet("Choose how ZeroClaw stores and searches memories."); + print_bullet("You can always change this later in config.toml."); + println!(); + + let options = vec![ + "SQLite with Vector Search (recommended) — fast, hybrid search, embeddings", + "Markdown Files — simple, human-readable, no dependencies", + "None — disable persistent memory", + ]; + + let choice = Select::new() + .with_prompt(" Select memory backend") + .items(&options) + .default(0) + .interact()?; + + let backend = match choice { + 1 => "markdown", + 2 => "none", + _ => "sqlite", // 0 and any unexpected value defaults to sqlite + }; + + let auto_save = if backend == "none" { + false + } else { + let save = Confirm::new() + .with_prompt(" Auto-save conversations to memory?") + .default(true) + .interact()?; + save + }; + + println!( + " {} Memory: {} (auto-save: {})", + style("✓").green().bold(), + style(backend).green(), + if auto_save { "on" } else { "off" } + ); + + Ok(MemoryConfig { + backend: backend.to_string(), + auto_save, + hygiene_enabled: backend == "sqlite", // Only enable hygiene for SQLite + archive_after_days: if backend == "sqlite" { 7 } else { 0 }, + purge_after_days: if backend == "sqlite" { 30 } else { 0 }, + conversation_retention_days: 30, + embedding_provider: "none".to_string(), + embedding_model: "text-embedding-3-small".to_string(), + embedding_dimensions: 1536, + vector_weight: 0.7, + keyword_weight: 0.3, + embedding_cache_size: if backend == "sqlite" { 10000 } else { 0 }, + chunk_max_tokens: 512, + }) +} + // ── Step 3: Channels ──────────────────────────────────────────── #[allow(clippy::too_many_lines)] diff --git a/src/tools/browser.rs b/src/tools/browser.rs index f104c9d..5ee9505 100644 --- a/src/tools/browser.rs +++ b/src/tools/browser.rs @@ -207,6 +207,7 @@ impl BrowserTool { } /// Execute a browser action + #[allow(clippy::too_many_lines)] async fn execute_action(&self, action: BrowserAction) -> anyhow::Result { match action { BrowserAction::Open { url } => { @@ -342,6 +343,7 @@ impl BrowserTool { } } + #[allow(clippy::unnecessary_wraps, clippy::unused_self)] fn to_result(&self, resp: AgentBrowserResponse) -> anyhow::Result { if resp.success { let output = resp @@ -506,13 +508,16 @@ impl Tool for BrowserTool { "snapshot" => BrowserAction::Snapshot { interactive_only: args .get("interactive_only") - .and_then(|v| v.as_bool()) + .and_then(serde_json::Value::as_bool) .unwrap_or(true), // Default to interactive for AI compact: args .get("compact") - .and_then(|v| v.as_bool()) + .and_then(serde_json::Value::as_bool) .unwrap_or(true), - depth: args.get("depth").and_then(|v| v.as_u64()).map(|d| d as u32), + depth: args + .get("depth") + .and_then(serde_json::Value::as_u64) + .map(|d| u32::try_from(d).unwrap_or(u32::MAX)), }, "click" => { let selector = args @@ -566,7 +571,7 @@ impl Tool for BrowserTool { path: args.get("path").and_then(|v| v.as_str()).map(String::from), full_page: args .get("full_page") - .and_then(|v| v.as_bool()) + .and_then(serde_json::Value::as_bool) .unwrap_or(false), }, "wait" => BrowserAction::Wait { @@ -574,7 +579,7 @@ impl Tool for BrowserTool { .get("selector") .and_then(|v| v.as_str()) .map(String::from), - ms: args.get("ms").and_then(|v| v.as_u64()), + ms: args.get("ms").and_then(serde_json::Value::as_u64), text: args.get("text").and_then(|v| v.as_str()).map(String::from), }, "press" => { @@ -602,8 +607,8 @@ impl Tool for BrowserTool { direction: direction.into(), pixels: args .get("pixels") - .and_then(|v| v.as_u64()) - .map(|p| p as u32), + .and_then(serde_json::Value::as_u64) + .map(|p| u32::try_from(p).unwrap_or(u32::MAX)), } } "is_visible" => {