From ffb0863dc5f6852234e6c98424b68f24f5e0bdd4 Mon Sep 17 00:00:00 2001 From: MrFadiAi Date: Thu, 19 Feb 2026 02:02:09 +0100 Subject: [PATCH] Agent bug fixes --- src-tauri/src/commands/config.rs | 137 ++++++++-------- src-tauri/src/commands/installer.rs | 197 +++++++++++++++++++++++- src-tauri/src/main.rs | 1 + src/components/Agents/index.tsx | 101 ++++-------- src/components/Dashboard/SystemInfo.tsx | 26 ++++ 5 files changed, 316 insertions(+), 146 deletions(-) diff --git a/src-tauri/src/commands/config.rs b/src-tauri/src/commands/config.rs index 770fad0..791590d 100644 --- a/src-tauri/src/commands/config.rs +++ b/src-tauri/src/commands/config.rs @@ -2488,89 +2488,76 @@ pub async fn save_agent(agent: AgentInfo) -> Result { Vec::new() }; - // Ensure agent directory exists and has basic files (SOUL.md) - // Resolving workspace path: use provided workspace OR openclaw_home/agent.agent_dir OR openclaw_home/agents/agent.id - let openclaw_home = platform::get_config_dir(); - let workspace_path = if let Some(ws) = &agent.workspace { - ws.clone() - } else if let Some(adir) = &agent.agent_dir { - // agent_dir might be relative to openclaw_home - if std::path::Path::new(adir).is_absolute() { - adir.clone() + // For NEW agents: use `openclaw agents add --workspace ` to create proper directory structure + // The --workspace flag is required to make the CLI non-interactive + let is_new_agent = !list.iter().any(|a| a.get("id").and_then(|v| v.as_str()) == Some(&agent.id)); + + if is_new_agent { + let openclaw_home = platform::get_config_dir(); + let workspace_dir = if let Some(ws) = &agent.workspace { + ws.clone() + } else if agent.default == Some(true) { + std::path::Path::new(&openclaw_home).join("workspace").to_string_lossy().to_string() } else { - let path = std::path::Path::new(&openclaw_home).join(adir); - path.to_string_lossy().to_string() - } - } else { - // Default: ~/.openclaw/workspace for main, ~/.openclaw/workspace-{id} for others - let path = if agent.id == "main" { - std::path::Path::new(&openclaw_home).join("workspace") - } else { - std::path::Path::new(&openclaw_home).join(format!("workspace-{}", agent.id)) + std::path::Path::new(&openclaw_home).join(format!("workspace-{}", agent.id)).to_string_lossy().to_string() }; - path.to_string_lossy().to_string() - }; - - // Also ensure agentDir directory exists: ~/.openclaw/agents/{id}/agent - let agent_dir_path = std::path::Path::new(&openclaw_home).join("agents").join(&agent.id).join("agent"); - if !agent_dir_path.exists() { - info!("[Agents] Creating agent dir: {}", agent_dir_path.display()); - let _ = std::fs::create_dir_all(&agent_dir_path); - } - // Also create sessions directory - let sessions_dir = std::path::Path::new(&openclaw_home).join("agents").join(&agent.id).join("sessions"); - if !sessions_dir.exists() { - let _ = std::fs::create_dir_all(&sessions_dir); - } - - // Auto-create directory - if !file::file_exists(&workspace_path) { - info!("[Agents] Creating agent workspace: {}", workspace_path); - if let Err(e) = std::fs::create_dir_all(&workspace_path) { - error!("[Agents] Failed to create workspace directory: {}", e); - } - } - - // Auto-create SOUL.md if missing - let soul_path = std::path::Path::new(&workspace_path).join("SOUL.md"); - if !soul_path.exists() { - info!("[Agents] SOUL.md missing, creating default for agent: {}", agent.id); - // Try to copy from root SOUL.md first - let root_soul = std::path::Path::new(&openclaw_home).join("SOUL.md"); - if root_soul.exists() { - if let Err(e) = std::fs::copy(&root_soul, &soul_path) { - warn!("[Agents] Failed to copy root SOUL.md: {}", e); - // Fallback to default content - let default_soul = format!("# Identity for {}\n\nYou are an AI agent named {}.", agent.id, agent.id); - let _ = std::fs::write(&soul_path, default_soul); - } - } else { - // Write default content - let default_soul = format!("# Identity for {}\n\nYou are an AI agent named {}.", agent.id, agent.id); - if let Err(e) = std::fs::write(&soul_path, default_soul) { - error!("[Agents] Failed to write default SOUL.md: {}", e); + info!("[Agents] New agent '{}' — running `openclaw agents add --workspace {}`", agent.id, workspace_dir); + match shell::run_openclaw(&["agents", "add", &agent.id, "--workspace", &workspace_dir]) { + Ok(output) => { + info!("[Agents] openclaw agents add succeeded: {}", output); + } + Err(e) => { + // NOTE: The CLI may exit with code 1 due to TUI stdin issues in non-interactive mode, + // but it still writes the agent entry to openclaw.json successfully. + warn!("[Agents] openclaw agents add exited with error (may still have written config): {}", e); } } + + // CRITICAL: Always reload config after CLI runs — it may have written the entry + // even if exit code was non-zero (TUI library issue in non-interactive mode) + config = load_openclaw_config()?; + list = if let Some(arr) = config["agents"].get("list").and_then(|v| v.as_array()) { + arr.clone() + } else if let Some(obj) = config["agents"].get("list").and_then(|v| v.as_object()) { + obj.iter().map(|(id, val)| { + let mut entry = val.clone(); + entry["id"] = json!(id); + entry + }).collect() + } else { + Vec::new() + }; } - // Update config object with resolving defaults if needed (e.g. if we auto-created, maybe we should save the path?) - // Creating the object again to include updates if any - let mut final_agent_obj = agent_obj.clone(); - - // If workspace was not explicit in input but we determined it, should we save it? - // OpenClaw Core conventions: - // - if 'workspace' is set, it uses that absolute path. - // - if 'agentDir' is set, it uses ~/.openclaw/{agentDir} - // - if neither, it defaults to ~/.openclaw/agents/{id} - - // Changing the logic: We won't force 'workspace' into the config if it wasn't there, - // to rely on Core's default behavior, but we ensured the directory exists. - - - // Update or add the agent + // Update or add the agent — for CLI-created entries, merge our fields into existing entry if let Some(existing) = list.iter_mut().find(|a| a.get("id").and_then(|v| v.as_str()) == Some(&agent.id)) { - *existing = agent_obj; + // Merge: only overwrite fields the user explicitly set (non-empty) + if let Some(model) = &agent.model { + if !model.is_empty() { + existing["model"] = json!({ "primary": model }); + } + } + if let Some(is_default) = agent.default { + if is_default { + existing["default"] = json!(true); + } + } + if let Some(sub) = &agent.subagents { + if let Some(allow) = &sub.allow_agents { + if !allow.is_empty() { + existing["subagents"] = json!({ "allowAgents": allow }); + } + } + } + if let Some(sandbox) = agent.sandbox { + existing["sandbox"] = json!(sandbox); + } + if let Some(heartbeat) = &agent.heartbeat { + if !heartbeat.is_empty() { + existing["heartbeat"] = json!({ "every": heartbeat }); + } + } } else { list.push(agent_obj); } diff --git a/src-tauri/src/commands/installer.rs b/src-tauri/src/commands/installer.rs index 7da39d5..0474dab 100644 --- a/src-tauri/src/commands/installer.rs +++ b/src-tauri/src/commands/installer.rs @@ -20,6 +20,8 @@ pub struct EnvironmentStatus { pub openclaw_installed: bool, /// OpenClaw version pub openclaw_version: Option, + /// Whether gateway service is installed + pub gateway_service_installed: bool, /// Whether config directory exists pub config_dir_exists: bool, /// Whether everything is ready @@ -75,12 +77,22 @@ pub async fn check_environment() -> Result { info!("[Environment Check] OpenClaw: installed={}, version={:?}", openclaw_installed, openclaw_version); + // Check Gateway Service (only if OpenClaw is installed) + let gateway_service_installed = if openclaw_installed { + info!("[Environment Check] Checking Gateway Service..."); + let installed = check_gateway_installed(); + info!("[Environment Check] Gateway Service: installed={}", installed); + installed + } else { + false + }; + // Check config directory let config_dir = platform::get_config_dir(); let config_dir_exists = std::path::Path::new(&config_dir).exists(); info!("[Environment Check] Config directory: {}, exists={}", config_dir, config_dir_exists); - let ready = node_installed && node_version_ok && openclaw_installed; + let ready = node_installed && node_version_ok && openclaw_installed && gateway_service_installed; info!("[Environment Check] Environment ready status: ready={}", ready); Ok(EnvironmentStatus { @@ -91,6 +103,7 @@ pub async fn check_environment() -> Result { git_version, openclaw_installed, openclaw_version, + gateway_service_installed, config_dir_exists, ready, os, @@ -321,6 +334,188 @@ fn check_node_version_requirement(version: &Option) -> bool { } } +/// Check if gateway service is installed +fn check_gateway_installed() -> bool { + match shell::run_openclaw(&["gateway", "status"]) { + Ok(output) => { + let lower = output.to_lowercase(); + // If output contains "not installed" or "not found", it's not installed + if lower.contains("not installed") || lower.contains("not found") { + return false; + } + // If the command succeeded, consider it installed + true + } + Err(e) => { + let lower = e.to_lowercase(); + // Some versions return error when not installed + if lower.contains("not installed") || lower.contains("not found") { + return false; + } + // If the command itself failed (e.g. openclaw not found), not installed + debug!("[Environment Check] Gateway status check failed: {}", e); + false + } + } +} + +/// Install gateway service (opens elevated terminal) +#[command] +pub async fn install_gateway_service() -> Result { + info!("[Gateway Install] Starting gateway service installation..."); + let os = platform::get_os(); + info!("[Gateway Install] Detected operating system: {}", os); + + match os.as_str() { + "windows" => install_gateway_windows().await, + "macos" => install_gateway_macos().await, + "linux" => install_gateway_linux().await, + _ => Err(format!("Unsupported operating system: {}", os)), + } +} + +/// Install gateway service on Windows (elevated PowerShell) +async fn install_gateway_windows() -> Result { + info!("[Gateway Install] Opening elevated PowerShell for gateway install..."); + + // Find openclaw path to use in the script + let openclaw_path = shell::get_openclaw_path().unwrap_or_else(|| "openclaw".to_string()); + let escaped_path = openclaw_path.replace('\\', "\\\\"); + + let script = format!(r#" +Start-Process powershell -ArgumentList '-NoExit', '-Command', ' +Write-Host "========================================" -ForegroundColor Cyan +Write-Host " OpenClaw Gateway Service Installer" -ForegroundColor White +Write-Host "========================================" -ForegroundColor Cyan +Write-Host "" +Write-Host "Installing OpenClaw Gateway as a system service..." -ForegroundColor Yellow +Write-Host "" + +try {{ + & "{}" gateway install + Write-Host "" + Write-Host "Gateway service installed successfully!" -ForegroundColor Green +}} catch {{ + Write-Host "Installation failed: $_" -ForegroundColor Red +}} + +Write-Host "" +Write-Host "You can close this window and click Refresh in OpenClaw Manager." -ForegroundColor Cyan +Write-Host "" +Read-Host "Press Enter to close this window" +' -Verb RunAs +"#, escaped_path); + + match shell::run_powershell_output(&script) { + Ok(_) => { + info!("[Gateway Install] Elevated terminal launched successfully"); + Ok("Gateway install terminal opened with administrator privileges. Please complete the installation and click Refresh.".to_string()) + } + Err(e) => { + warn!("[Gateway Install] Failed to launch elevated terminal: {}", e); + Err(format!("Failed to open administrator terminal: {}. Please open PowerShell as Administrator and run: openclaw gateway install", e)) + } + } +} + +/// Install gateway service on macOS (Terminal with sudo) +async fn install_gateway_macos() -> Result { + info!("[Gateway Install] Opening terminal for gateway install on macOS..."); + + let script_content = r#"#!/bin/bash +clear +echo "========================================" +echo " OpenClaw Gateway Service Installer" +echo "========================================" +echo "" +echo "Installing OpenClaw Gateway as a system service..." +echo "You may be prompted for your password." +echo "" + +sudo openclaw gateway install + +echo "" +if [ $? -eq 0 ]; then + echo "✅ Gateway service installed successfully!" +else + echo "❌ Installation failed. Please check the error above." +fi +echo "" +echo "You can close this window and click Refresh in OpenClaw Manager." +read -p "Press Enter to close this window..." +"#; + + let script_path = "/tmp/openclaw_gateway_install.command"; + std::fs::write(script_path, script_content) + .map_err(|e| format!("Failed to create script: {}", e))?; + + std::process::Command::new("chmod") + .args(["+x", script_path]) + .output() + .map_err(|e| format!("Failed to set permissions: {}", e))?; + + std::process::Command::new("open") + .arg(script_path) + .spawn() + .map_err(|e| format!("Failed to launch terminal: {}", e))?; + + info!("[Gateway Install] Terminal launched successfully on macOS"); + Ok("Gateway install terminal opened. Please enter your password when prompted and click Refresh after completion.".to_string()) +} + +/// Install gateway service on Linux (terminal with sudo) +async fn install_gateway_linux() -> Result { + info!("[Gateway Install] Opening terminal for gateway install on Linux..."); + + let script_content = r#"#!/bin/bash +clear +echo "========================================" +echo " OpenClaw Gateway Service Installer" +echo "========================================" +echo "" +echo "Installing OpenClaw Gateway as a system service..." +echo "You may be prompted for your password." +echo "" + +sudo openclaw gateway install + +echo "" +if [ $? -eq 0 ]; then + echo "✅ Gateway service installed successfully!" +else + echo "❌ Installation failed. Please check the error above." +fi +echo "" +echo "You can close this window and click Refresh in OpenClaw Manager." +read -p "Press Enter to close this window..." +"#; + + let script_path = "/tmp/openclaw_gateway_install.sh"; + std::fs::write(script_path, script_content) + .map_err(|e| format!("Failed to create script: {}", e))?; + + std::process::Command::new("chmod") + .args(["+x", script_path]) + .output() + .map_err(|e| format!("Failed to set permissions: {}", e))?; + + // Try different terminal emulators + let terminals = ["gnome-terminal", "xfce4-terminal", "konsole", "xterm"]; + for term in terminals { + if std::process::Command::new(term) + .args(["--", script_path]) + .spawn() + .is_ok() + { + info!("[Gateway Install] Terminal '{}' launched successfully on Linux", term); + return Ok("Gateway install terminal opened. Please enter your password when prompted and click Refresh after completion.".to_string()); + } + } + + warn!("[Gateway Install] No terminal emulator found on Linux"); + Err("Unable to launch terminal. Please open a terminal and run: sudo openclaw gateway install".to_string()) +} + /// Install Node.js #[command] pub async fn install_nodejs() -> Result { diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index b39b5d0..e9219bc 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -91,6 +91,7 @@ fn main() { installer::init_openclaw_config, installer::open_install_terminal, installer::uninstall_openclaw, + installer::install_gateway_service, // Version update installer::check_openclaw_update, installer::update_openclaw, diff --git a/src/components/Agents/index.tsx b/src/components/Agents/index.tsx index b2854ae..2c4ac45 100644 --- a/src/components/Agents/index.tsx +++ b/src/components/Agents/index.tsx @@ -140,7 +140,6 @@ export function Agents() { const [wizardForm, setWizardForm] = useState({ botAccountId: '', agentId: '', - systemPrompt: '', model: '', isDefault: false, }); @@ -285,24 +284,12 @@ export function Agents() { const handleWizardSubmit = async () => { setSaving(true); try { - // 1. Save agent - // Auto-configure workspace path based on Default Agent toggle - const agentWorkspace = openclawHomeDir - ? (wizardForm.isDefault - ? `${openclawHomeDir}/workspace` - : `${openclawHomeDir}/workspace-${wizardForm.agentId}` - ).replace(/\\/g, '/') - : null; - // Agent directory: agents/{id}/agent - const agentDirPath = openclawHomeDir - ? `${openclawHomeDir}/agents/${wizardForm.agentId}/agent`.replace(/\\/g, '/') - : null; - + // Save agent — backend will run `openclaw agents add` for proper structure const agent: AgentInfo = { id: wizardForm.agentId, name: null, - workspace: agentWorkspace, - agent_dir: agentDirPath, + workspace: null, + agent_dir: null, model: wizardForm.model || null, sandbox: null, heartbeat: null, @@ -311,21 +298,11 @@ export function Agents() { }; await invoke('save_agent', { agent }); - // 2. Save system prompt if provided - if (wizardForm.systemPrompt.trim()) { - await invoke('save_agent_system_prompt', { - agentId: wizardForm.agentId, - workspace: openclawHomeDir || null, - content: wizardForm.systemPrompt - }); - } - // Note: save_agent auto-creates binding if matching account exists setShowWizardDialog(false); setWizardStep(0); - setWizardStep(0); - setWizardForm({ botAccountId: '', agentId: '', systemPrompt: '', model: '', isDefault: false }); + setWizardForm({ botAccountId: '', agentId: '', model: '', isDefault: false }); fetchData(); } catch (e) { setError(String(e)); @@ -420,7 +397,6 @@ export function Agents() { setWizardForm({ botAccountId: telegramAccounts[0]?.id || '', agentId: '', - systemPrompt: '', model: '', isDefault: false, }); @@ -820,27 +796,29 @@ export function Agents() { /> - {/* System Prompt Editor (actually SOUL.md in OpenClaw) */} -
- - {loadingPrompt ? ( -
- Loading... -
- ) : ( -