mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-26 23:50:17 +00:00
feat(setup): quick onboarding, preserve .env vars, clean up boot logging (#821)
* feat(setup): quick onboarding, preserve .env vars, clean up boot logging (#751, #674) - Add upsert_bootstrap_vars() to preserve user-added .env vars on re-onboarding - Add --quick mode: auto-defaults DB + security, asks only LLM provider (2 steps) - Auto-triggered onboarding uses quick mode for near-instant first run - Fix NEAR AI model fetch to use cloud-api.near.ai when API key is set - Handle missing WASM tools/channels directories gracefully - Downgrade all boot/shutdown tracing::info! to debug (boot screen shows user output) Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix(setup): gate env_backend variable behind postgres feature flag [skip-regression-check] Clippy lint fix — not a behavioral change, just moving a variable declaration inside the cfg(feature = "postgres") block where it's used. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix(review): address PR review comments - WASM loaders: use tokio::fs::metadata, only treat NotFound as empty, propagate other IO errors, handle TOCTOU in read_dir - bootstrap: only ignore NotFound in read_to_string, propagate other errors - wizard: restore print_info/print_success for migrations in interactive mode (gated by !config.quick), keep tracing::debug for diagnostics - tests: use shared crate::config::helpers::ENV_MUTEX instead of separate NEARAI_ENV_MUTEX to prevent cross-test env var races - README: fix quick mode description to mention model selection, clarify auto_setup_database may prompt when DATABASE_URL is set Co-Authored-By: Claude Opus 4.6 <[email protected]> * feat(setup): skip prompts for DATABASE_URL in quick mode [skip-regression-check] auto_setup_database() now uses DATABASE_URL directly without calling step_database_postgres() (which prompts for confirmation). Quick mode should be fully non-interactive when env vars are already set. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix(cli): update --quick help text to mention model selection [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
94d101924e
commit
3a2989d009
+14
-14
@@ -241,7 +241,7 @@ impl ToolRegistry {
|
||||
}
|
||||
self.register_sync(Arc::new(http));
|
||||
|
||||
tracing::info!("Registered {} built-in tools", self.count());
|
||||
tracing::debug!("Registered {} built-in tools", self.count());
|
||||
}
|
||||
|
||||
/// Register only orchestrator-domain tools (safe for the main process).
|
||||
@@ -289,7 +289,7 @@ impl ToolRegistry {
|
||||
self.register_sync(Arc::new(ListDirTool::new()));
|
||||
self.register_sync(Arc::new(ApplyPatchTool::new()));
|
||||
|
||||
tracing::info!("Registered 5 development tools");
|
||||
tracing::debug!("Registered 5 development tools");
|
||||
}
|
||||
|
||||
/// Register memory tools with a workspace.
|
||||
@@ -302,7 +302,7 @@ impl ToolRegistry {
|
||||
self.register_sync(Arc::new(MemoryReadTool::new(Arc::clone(&workspace))));
|
||||
self.register_sync(Arc::new(MemoryTreeTool::new(workspace)));
|
||||
|
||||
tracing::info!("Registered 4 memory tools");
|
||||
tracing::debug!("Registered 4 memory tools");
|
||||
}
|
||||
|
||||
/// Register job management tools.
|
||||
@@ -364,7 +364,7 @@ impl ToolRegistry {
|
||||
job_tool_count += 1;
|
||||
}
|
||||
|
||||
tracing::info!("Registered {} job management tools", job_tool_count);
|
||||
tracing::debug!("Registered {} job management tools", job_tool_count);
|
||||
}
|
||||
|
||||
/// Register secret management tools (list, delete).
|
||||
@@ -378,7 +378,7 @@ impl ToolRegistry {
|
||||
use crate::tools::builtin::{SecretDeleteTool, SecretListTool};
|
||||
self.register_sync(Arc::new(SecretListTool::new(Arc::clone(&store))));
|
||||
self.register_sync(Arc::new(SecretDeleteTool::new(store)));
|
||||
tracing::info!("Registered 2 secret management tools (list, delete)");
|
||||
tracing::debug!("Registered 2 secret management tools (list, delete)");
|
||||
}
|
||||
|
||||
/// Register extension management tools (search, install, auth, activate, list, remove).
|
||||
@@ -393,7 +393,7 @@ impl ToolRegistry {
|
||||
self.register_sync(Arc::new(ToolRemoveTool::new(Arc::clone(&manager))));
|
||||
self.register_sync(Arc::new(ToolUpgradeTool::new(Arc::clone(&manager))));
|
||||
self.register_sync(Arc::new(ExtensionInfoTool::new(manager)));
|
||||
tracing::info!("Registered 8 extension management tools");
|
||||
tracing::debug!("Registered 8 extension management tools");
|
||||
}
|
||||
|
||||
/// Register skill management tools (list, search, install, remove).
|
||||
@@ -414,7 +414,7 @@ impl ToolRegistry {
|
||||
Arc::clone(&catalog),
|
||||
)));
|
||||
self.register_sync(Arc::new(SkillRemoveTool::new(registry)));
|
||||
tracing::info!("Registered 4 skill management tools");
|
||||
tracing::debug!("Registered 4 skill management tools");
|
||||
}
|
||||
|
||||
/// Register routine management tools.
|
||||
@@ -448,7 +448,7 @@ impl ToolRegistry {
|
||||
Arc::clone(&engine),
|
||||
)));
|
||||
self.register_sync(Arc::new(RoutineHistoryTool::new(store)));
|
||||
tracing::info!("Registered 6 routine management tools");
|
||||
tracing::debug!("Registered 6 routine management tools");
|
||||
}
|
||||
|
||||
/// Register message tool for sending messages to channels.
|
||||
@@ -467,7 +467,7 @@ impl ToolRegistry {
|
||||
.write()
|
||||
.await
|
||||
.insert("message".to_string());
|
||||
tracing::info!("Registered message tool");
|
||||
tracing::debug!("Registered message tool");
|
||||
}
|
||||
|
||||
/// Set the default channel and target for the message tool.
|
||||
@@ -501,7 +501,7 @@ impl ToolRegistry {
|
||||
gen_model,
|
||||
base_dir,
|
||||
)));
|
||||
tracing::info!("Registered 2 image tools (generate, edit)");
|
||||
tracing::debug!("Registered 2 image tools (generate, edit)");
|
||||
}
|
||||
|
||||
/// Register vision/image analysis tools.
|
||||
@@ -521,7 +521,7 @@ impl ToolRegistry {
|
||||
vision_model,
|
||||
base_dir,
|
||||
)));
|
||||
tracing::info!("Registered 1 vision tool (analyze)");
|
||||
tracing::debug!("Registered 1 vision tool (analyze)");
|
||||
}
|
||||
|
||||
/// Register the software builder tool.
|
||||
@@ -549,7 +549,7 @@ impl ToolRegistry {
|
||||
self.register(Arc::new(BuildSoftwareTool::new(builder)))
|
||||
.await;
|
||||
|
||||
tracing::info!("Registered software builder tool");
|
||||
tracing::debug!("Registered software builder tool");
|
||||
}
|
||||
|
||||
/// Register a WASM tool from bytes.
|
||||
@@ -619,7 +619,7 @@ impl ToolRegistry {
|
||||
);
|
||||
}
|
||||
|
||||
tracing::info!(name = reg.name, "Registered WASM tool");
|
||||
tracing::debug!(name = reg.name, "Registered WASM tool");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -676,7 +676,7 @@ impl ToolRegistry {
|
||||
.await
|
||||
.map_err(WasmRegistrationError::Wasm)?;
|
||||
|
||||
tracing::info!(
|
||||
tracing::debug!(
|
||||
name = tool_with_binary.tool.name,
|
||||
user_id = user_id,
|
||||
trust_level = %tool_with_binary.tool.trust_level,
|
||||
|
||||
@@ -193,18 +193,31 @@ impl WasmToolLoader {
|
||||
///
|
||||
/// Tools without a capabilities file get no permissions (default deny).
|
||||
pub async fn load_from_dir(&self, dir: &Path) -> Result<LoadResults, WasmLoadError> {
|
||||
if !dir.is_dir() {
|
||||
return Err(WasmLoadError::Io(std::io::Error::new(
|
||||
std::io::ErrorKind::NotADirectory,
|
||||
format!("{} is not a directory", dir.display()),
|
||||
)));
|
||||
match fs::metadata(dir).await {
|
||||
Ok(meta) if meta.is_dir() => {}
|
||||
Ok(_) => {
|
||||
return Err(WasmLoadError::Io(std::io::Error::new(
|
||||
std::io::ErrorKind::NotADirectory,
|
||||
format!("{} is not a directory", dir.display()),
|
||||
)));
|
||||
}
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
||||
return Ok(LoadResults::default());
|
||||
}
|
||||
Err(e) => return Err(WasmLoadError::Io(e)),
|
||||
}
|
||||
|
||||
let mut results = LoadResults::default();
|
||||
// Handle TOCTOU: if read_dir fails with NotFound, treat as empty
|
||||
let mut entries = match fs::read_dir(dir).await {
|
||||
Ok(entries) => entries,
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
||||
return Ok(LoadResults::default());
|
||||
}
|
||||
Err(e) => return Err(WasmLoadError::Io(e)),
|
||||
};
|
||||
|
||||
// Collect all .wasm entries first, then load in parallel
|
||||
let mut results = LoadResults::default();
|
||||
let mut tool_entries = Vec::new();
|
||||
let mut entries = fs::read_dir(dir).await?;
|
||||
|
||||
while let Some(entry) = entries.next_entry().await? {
|
||||
let path = entry.path();
|
||||
@@ -1077,4 +1090,19 @@ mod tests {
|
||||
"nested.wasm inside subdir should NOT be discovered"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn load_from_dir_returns_empty_when_dir_missing() {
|
||||
let loader = make_loader();
|
||||
|
||||
let dir = TempDir::new().unwrap();
|
||||
let missing = dir.path().join("nonexistent_tools_dir");
|
||||
|
||||
let results = loader.load_from_dir(&missing).await;
|
||||
|
||||
// Must succeed with empty results, not error
|
||||
let results = results.expect("missing dir should return Ok, not Err");
|
||||
assert!(results.loaded.is_empty());
|
||||
assert!(results.errors.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user