mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-09-02 09:39:37 +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
@@ -75,7 +75,7 @@ impl ChannelManager {
|
||||
break;
|
||||
}
|
||||
}
|
||||
tracing::info!(channel = %name, "Hot-added channel stream ended");
|
||||
tracing::debug!(channel = %name, "Hot-added channel stream ended");
|
||||
});
|
||||
|
||||
Ok(())
|
||||
@@ -92,7 +92,7 @@ impl ChannelManager {
|
||||
for (name, channel) in channels.iter() {
|
||||
match channel.start().await {
|
||||
Ok(stream) => {
|
||||
tracing::info!("Started channel: {}", name);
|
||||
tracing::debug!("Started channel: {}", name);
|
||||
streams.push(stream);
|
||||
}
|
||||
Err(e) => {
|
||||
|
||||
@@ -184,18 +184,32 @@ impl WasmChannelLoader {
|
||||
/// └── telegram.capabilities.json
|
||||
/// ```
|
||||
pub async fn load_from_dir(&self, dir: &Path) -> Result<LoadResults, WasmChannelError> {
|
||||
if !dir.is_dir() {
|
||||
return Err(WasmChannelError::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(WasmChannelError::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(WasmChannelError::Io(e)),
|
||||
}
|
||||
|
||||
let mut results = LoadResults::default();
|
||||
|
||||
// Collect all .wasm entries first, then load in parallel
|
||||
let mut channel_entries = Vec::new();
|
||||
let mut entries = fs::read_dir(dir).await?;
|
||||
// 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(WasmChannelError::Io(e)),
|
||||
};
|
||||
|
||||
while let Some(entry) = entries.next_entry().await? {
|
||||
let path = entry.path();
|
||||
@@ -486,4 +500,21 @@ mod tests {
|
||||
let result = loader.load_from_files("", &wasm_path, None).await;
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn load_from_dir_returns_empty_when_dir_missing() {
|
||||
let config = WasmChannelRuntimeConfig::for_testing();
|
||||
let runtime = Arc::new(WasmChannelRuntime::new(config).unwrap());
|
||||
let loader = WasmChannelLoader::new(runtime, Arc::new(PairingStore::new()), None);
|
||||
|
||||
let dir = TempDir::new().unwrap();
|
||||
let missing = dir.path().join("nonexistent_channels_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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -370,7 +370,7 @@ pub async fn start_server(
|
||||
if let Err(e) = axum::serve(listener, app)
|
||||
.with_graceful_shutdown(async {
|
||||
let _ = shutdown_rx.await;
|
||||
tracing::info!("Web gateway shutting down");
|
||||
tracing::debug!("Web gateway shutting down");
|
||||
})
|
||||
.await
|
||||
{
|
||||
|
||||
@@ -68,7 +68,7 @@ impl WebhookServer {
|
||||
if let Err(e) = axum::serve(listener, app)
|
||||
.with_graceful_shutdown(async {
|
||||
let _ = shutdown_rx.await;
|
||||
tracing::info!("Webhook server shutting down");
|
||||
tracing::debug!("Webhook server shutting down");
|
||||
})
|
||||
.await
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user