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)
This commit is contained in:
parent
554f6e9ea5
commit
2c7021e90f
3 changed files with 117 additions and 25 deletions
11
src/main.rs
11
src/main.rs
|
|
@ -81,6 +81,10 @@ enum Commands {
|
||||||
/// Provider name (used in quick mode, default: openrouter)
|
/// Provider name (used in quick mode, default: openrouter)
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
provider: Option<String>,
|
provider: Option<String>,
|
||||||
|
|
||||||
|
/// Memory backend (sqlite, markdown, none) - used in quick mode, default: sqlite
|
||||||
|
#[arg(long)]
|
||||||
|
memory: Option<String>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Start the AI agent loop
|
/// Start the AI agent loop
|
||||||
|
|
@ -264,13 +268,14 @@ async fn main() -> Result<()> {
|
||||||
channels_only,
|
channels_only,
|
||||||
api_key,
|
api_key,
|
||||||
provider,
|
provider,
|
||||||
|
memory,
|
||||||
} = &cli.command
|
} = &cli.command
|
||||||
{
|
{
|
||||||
if *interactive && *channels_only {
|
if *interactive && *channels_only {
|
||||||
bail!("Use either --interactive or --channels-only, not both");
|
bail!("Use either --interactive or --channels-only, not both");
|
||||||
}
|
}
|
||||||
if *channels_only && (api_key.is_some() || provider.is_some()) {
|
if *channels_only && (api_key.is_some() || provider.is_some() || memory.is_some()) {
|
||||||
bail!("--channels-only does not accept --api-key or --provider");
|
bail!("--channels-only does not accept --api-key, --provider, or --memory");
|
||||||
}
|
}
|
||||||
|
|
||||||
let config = if *channels_only {
|
let config = if *channels_only {
|
||||||
|
|
@ -278,7 +283,7 @@ async fn main() -> Result<()> {
|
||||||
} else if *interactive {
|
} else if *interactive {
|
||||||
onboard::run_wizard()?
|
onboard::run_wizard()?
|
||||||
} else {
|
} 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
|
// Auto-start channels if user said yes during wizard
|
||||||
if std::env::var("ZEROCLAW_AUTOSTART_CHANNELS").as_deref() == Ok("1") {
|
if std::env::var("ZEROCLAW_AUTOSTART_CHANNELS").as_deref() == Ok("1") {
|
||||||
|
|
|
||||||
|
|
@ -55,25 +55,28 @@ pub fn run_wizard() -> Result<Config> {
|
||||||
);
|
);
|
||||||
println!();
|
println!();
|
||||||
|
|
||||||
print_step(1, 7, "Workspace Setup");
|
print_step(1, 8, "Workspace Setup");
|
||||||
let (workspace_dir, config_path) = setup_workspace()?;
|
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()?;
|
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()?;
|
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()?;
|
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()?;
|
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()?;
|
let project_ctx = setup_project_context()?;
|
||||||
|
|
||||||
print_step(7, 7, "Workspace Files");
|
print_step(8, 8, "Workspace Files");
|
||||||
scaffold_workspace(&workspace_dir, &project_ctx)?;
|
scaffold_workspace(&workspace_dir, &project_ctx)?;
|
||||||
|
|
||||||
// ── Build config ──
|
// ── Build config ──
|
||||||
|
|
@ -95,7 +98,7 @@ pub fn run_wizard() -> Result<Config> {
|
||||||
reliability: crate::config::ReliabilityConfig::default(),
|
reliability: crate::config::ReliabilityConfig::default(),
|
||||||
heartbeat: HeartbeatConfig::default(),
|
heartbeat: HeartbeatConfig::default(),
|
||||||
channels_config,
|
channels_config,
|
||||||
memory: MemoryConfig::default(), // SQLite + auto-save by default
|
memory: memory_config, // User-selected memory backend
|
||||||
tunnel: tunnel_config,
|
tunnel: tunnel_config,
|
||||||
gateway: crate::config::GatewayConfig::default(),
|
gateway: crate::config::GatewayConfig::default(),
|
||||||
composio: composio_config,
|
composio: composio_config,
|
||||||
|
|
@ -110,9 +113,10 @@ pub fn run_wizard() -> Result<Config> {
|
||||||
style("Supervised").green()
|
style("Supervised").green()
|
||||||
);
|
);
|
||||||
println!(
|
println!(
|
||||||
" {} Memory: {} (auto-save: on)",
|
" {} Memory: {} (auto-save: {})",
|
||||||
style("✓").green().bold(),
|
style("✓").green().bold(),
|
||||||
style("sqlite").green()
|
style(&config.memory.backend).green(),
|
||||||
|
if config.memory.auto_save { "on" } else { "off" }
|
||||||
);
|
);
|
||||||
|
|
||||||
config.save()?;
|
config.save()?;
|
||||||
|
|
@ -210,10 +214,10 @@ pub fn run_channels_repair_wizard() -> Result<Config> {
|
||||||
// ── Quick setup (zero prompts) ───────────────────────────────────
|
// ── Quick setup (zero prompts) ───────────────────────────────────
|
||||||
|
|
||||||
/// Non-interactive setup: generates a sensible default config instantly.
|
/// 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.
|
/// Use `zeroclaw onboard --interactive` for the full wizard.
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
pub fn run_quick_setup(api_key: Option<&str>, provider: Option<&str>) -> Result<Config> {
|
pub fn run_quick_setup(api_key: Option<&str>, provider: Option<&str>, memory_backend: Option<&str>) -> Result<Config> {
|
||||||
println!("{}", style(BANNER).cyan().bold());
|
println!("{}", style(BANNER).cyan().bold());
|
||||||
println!(
|
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 provider_name = provider.unwrap_or("openrouter").to_string();
|
||||||
let model = default_model_for_provider(&provider_name);
|
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 {
|
let config = Config {
|
||||||
workspace_dir: workspace_dir.clone(),
|
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(),
|
reliability: crate::config::ReliabilityConfig::default(),
|
||||||
heartbeat: HeartbeatConfig::default(),
|
heartbeat: HeartbeatConfig::default(),
|
||||||
channels_config: ChannelsConfig::default(),
|
channels_config: ChannelsConfig::default(),
|
||||||
memory: MemoryConfig::default(),
|
memory: memory_config,
|
||||||
tunnel: crate::config::TunnelConfig::default(),
|
tunnel: crate::config::TunnelConfig::default(),
|
||||||
gateway: crate::config::GatewayConfig::default(),
|
gateway: crate::config::GatewayConfig::default(),
|
||||||
composio: ComposioConfig::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()
|
style("Supervised (workspace-scoped)").green()
|
||||||
);
|
);
|
||||||
println!(
|
println!(
|
||||||
" {} Memory: {}",
|
" {} Memory: {} (auto-save: {})",
|
||||||
style("✓").green().bold(),
|
style("✓").green().bold(),
|
||||||
style("sqlite (auto-save)").green()
|
style(&memory_backend_name).green(),
|
||||||
|
if memory_backend_name == "none" { "off" } else { "on" }
|
||||||
);
|
);
|
||||||
println!(
|
println!(
|
||||||
" {} Secrets: {}",
|
" {} Secrets: {}",
|
||||||
|
|
@ -932,6 +955,65 @@ fn setup_project_context() -> Result<ProjectContext> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Step 6: Memory Configuration ───────────────────────────────
|
||||||
|
|
||||||
|
fn setup_memory() -> Result<MemoryConfig> {
|
||||||
|
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 ────────────────────────────────────────────
|
// ── Step 3: Channels ────────────────────────────────────────────
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
|
|
|
||||||
|
|
@ -207,6 +207,7 @@ impl BrowserTool {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execute a browser action
|
/// Execute a browser action
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
async fn execute_action(&self, action: BrowserAction) -> anyhow::Result<ToolResult> {
|
async fn execute_action(&self, action: BrowserAction) -> anyhow::Result<ToolResult> {
|
||||||
match action {
|
match action {
|
||||||
BrowserAction::Open { url } => {
|
BrowserAction::Open { url } => {
|
||||||
|
|
@ -342,6 +343,7 @@ impl BrowserTool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::unnecessary_wraps, clippy::unused_self)]
|
||||||
fn to_result(&self, resp: AgentBrowserResponse) -> anyhow::Result<ToolResult> {
|
fn to_result(&self, resp: AgentBrowserResponse) -> anyhow::Result<ToolResult> {
|
||||||
if resp.success {
|
if resp.success {
|
||||||
let output = resp
|
let output = resp
|
||||||
|
|
@ -506,13 +508,16 @@ impl Tool for BrowserTool {
|
||||||
"snapshot" => BrowserAction::Snapshot {
|
"snapshot" => BrowserAction::Snapshot {
|
||||||
interactive_only: args
|
interactive_only: args
|
||||||
.get("interactive_only")
|
.get("interactive_only")
|
||||||
.and_then(|v| v.as_bool())
|
.and_then(serde_json::Value::as_bool)
|
||||||
.unwrap_or(true), // Default to interactive for AI
|
.unwrap_or(true), // Default to interactive for AI
|
||||||
compact: args
|
compact: args
|
||||||
.get("compact")
|
.get("compact")
|
||||||
.and_then(|v| v.as_bool())
|
.and_then(serde_json::Value::as_bool)
|
||||||
.unwrap_or(true),
|
.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" => {
|
"click" => {
|
||||||
let selector = args
|
let selector = args
|
||||||
|
|
@ -566,7 +571,7 @@ impl Tool for BrowserTool {
|
||||||
path: args.get("path").and_then(|v| v.as_str()).map(String::from),
|
path: args.get("path").and_then(|v| v.as_str()).map(String::from),
|
||||||
full_page: args
|
full_page: args
|
||||||
.get("full_page")
|
.get("full_page")
|
||||||
.and_then(|v| v.as_bool())
|
.and_then(serde_json::Value::as_bool)
|
||||||
.unwrap_or(false),
|
.unwrap_or(false),
|
||||||
},
|
},
|
||||||
"wait" => BrowserAction::Wait {
|
"wait" => BrowserAction::Wait {
|
||||||
|
|
@ -574,7 +579,7 @@ impl Tool for BrowserTool {
|
||||||
.get("selector")
|
.get("selector")
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.map(String::from),
|
.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),
|
text: args.get("text").and_then(|v| v.as_str()).map(String::from),
|
||||||
},
|
},
|
||||||
"press" => {
|
"press" => {
|
||||||
|
|
@ -602,8 +607,8 @@ impl Tool for BrowserTool {
|
||||||
direction: direction.into(),
|
direction: direction.into(),
|
||||||
pixels: args
|
pixels: args
|
||||||
.get("pixels")
|
.get("pixels")
|
||||||
.and_then(|v| v.as_u64())
|
.and_then(serde_json::Value::as_u64)
|
||||||
.map(|p| p as u32),
|
.map(|p| u32::try_from(p).unwrap_or(u32::MAX)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"is_visible" => {
|
"is_visible" => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue