mirror of
https://github.com/MrFadiAi/openclaw-manager.git
synced 2026-08-14 00:57:59 +00:00
Agent bug fixes
This commit is contained in:
@@ -2488,89 +2488,76 @@ pub async fn save_agent(agent: AgentInfo) -> Result<String, String> {
|
||||
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 <id> --workspace <dir>` 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);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ pub struct EnvironmentStatus {
|
||||
pub openclaw_installed: bool,
|
||||
/// OpenClaw version
|
||||
pub openclaw_version: Option<String>,
|
||||
/// 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<EnvironmentStatus, String> {
|
||||
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<EnvironmentStatus, String> {
|
||||
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<String>) -> 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<String, String> {
|
||||
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<String, String> {
|
||||
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<String, String> {
|
||||
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<String, String> {
|
||||
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<InstallResult, String> {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* System Prompt Editor (actually SOUL.md in OpenClaw) */}
|
||||
<div>
|
||||
<label className="block text-sm text-gray-400 mb-1 flex items-center gap-2">
|
||||
<FileText size={14} />
|
||||
Personality (SOUL.md)
|
||||
</label>
|
||||
{loadingPrompt ? (
|
||||
<div className="flex items-center gap-2 text-gray-500 text-sm py-4">
|
||||
<Loader2 className="animate-spin" size={14} /> Loading...
|
||||
</div>
|
||||
) : (
|
||||
<textarea
|
||||
value={systemPrompt}
|
||||
onChange={e => setSystemPrompt(e.target.value)}
|
||||
className="input-base font-mono text-sm"
|
||||
rows={6}
|
||||
placeholder="You are a coding expert specializing in..."
|
||||
/>
|
||||
)}
|
||||
<p className="text-xs text-gray-500 mt-1">Defines the agent's personality. Saved to <code className="text-gray-400">{agentForm.workspace || `workspace-${agentForm.id || '...'}`}/SOUL.md</code></p>
|
||||
</div>
|
||||
{/* System Prompt Editor — only shown when editing existing agents */}
|
||||
{editingAgent && (
|
||||
<div>
|
||||
<label className="block text-sm text-gray-400 mb-1 flex items-center gap-2">
|
||||
<FileText size={14} />
|
||||
Personality (SOUL.md)
|
||||
</label>
|
||||
{loadingPrompt ? (
|
||||
<div className="flex items-center gap-2 text-gray-500 text-sm py-4">
|
||||
<Loader2 className="animate-spin" size={14} /> Loading...
|
||||
</div>
|
||||
) : (
|
||||
<textarea
|
||||
value={systemPrompt}
|
||||
onChange={e => setSystemPrompt(e.target.value)}
|
||||
className="input-base font-mono text-sm"
|
||||
rows={6}
|
||||
placeholder="You are a coding expert specializing in..."
|
||||
/>
|
||||
)}
|
||||
<p className="text-xs text-gray-500 mt-1">Defines the agent's personality. Saved to <code className="text-gray-400">{agentForm.workspace || `workspace-${agentForm.id || '...'}`}/SOUL.md</code></p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-2 pt-2">
|
||||
<input
|
||||
@@ -1017,7 +995,7 @@ export function Agents() {
|
||||
|
||||
{/* Step indicators */}
|
||||
<div className="px-6 pt-4 flex gap-2">
|
||||
{['Bot Account', 'Agent Config', 'Personality'].map((label, i) => (
|
||||
{['Bot Account', 'Agent Config'].map((label, i) => (
|
||||
<div key={i} className="flex-1">
|
||||
<div className={`h-1 rounded-full transition-colors ${i <= wizardStep ? 'bg-claw-500' : 'bg-dark-600'}`} />
|
||||
<p className={`text-xs mt-1 ${i <= wizardStep ? 'text-claw-400' : 'text-gray-600'}`}>{label}</p>
|
||||
@@ -1086,24 +1064,7 @@ export function Agents() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{wizardStep === 2 && (
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-gray-300">Define the personality for <span className="text-claw-400 font-medium">{wizardForm.agentId}</span>.</p>
|
||||
<div>
|
||||
<label className="block text-sm text-gray-400 mb-1 flex items-center gap-2">
|
||||
<FileText size={14} />
|
||||
Personality (SOUL.md)
|
||||
</label>
|
||||
<textarea
|
||||
value={wizardForm.systemPrompt}
|
||||
onChange={e => setWizardForm({ ...wizardForm, systemPrompt: e.target.value })}
|
||||
className="input-base font-mono text-sm"
|
||||
rows={6}
|
||||
placeholder="You are a coding expert. You help users write clean, efficient code..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
|
||||
<div className="px-6 py-4 border-t border-dark-600 flex justify-between">
|
||||
@@ -1113,10 +1074,10 @@ export function Agents() {
|
||||
>
|
||||
{wizardStep === 0 ? 'Cancel' : 'Back'}
|
||||
</button>
|
||||
{wizardStep < 2 ? (
|
||||
{wizardStep < 1 ? (
|
||||
<button
|
||||
onClick={() => setWizardStep(wizardStep + 1)}
|
||||
disabled={wizardStep === 0 ? !wizardForm.botAccountId : !wizardForm.agentId}
|
||||
disabled={!wizardForm.botAccountId}
|
||||
className="btn-primary flex items-center gap-2"
|
||||
>
|
||||
Next
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
Package,
|
||||
Shield,
|
||||
RefreshCw,
|
||||
Server,
|
||||
} from 'lucide-react';
|
||||
import { isTauri } from '../../lib/tauri';
|
||||
|
||||
@@ -22,6 +23,7 @@ interface EnvironmentStatus {
|
||||
git_version: string | null;
|
||||
openclaw_installed: boolean;
|
||||
openclaw_version: string | null;
|
||||
gateway_service_installed: boolean;
|
||||
config_dir_exists: boolean;
|
||||
ready: boolean;
|
||||
os: string;
|
||||
@@ -115,6 +117,20 @@ export function SystemInfo() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleInstallGateway = async () => {
|
||||
setInstalling('gateway');
|
||||
setError(null);
|
||||
try {
|
||||
await invoke<string>('install_gateway_service');
|
||||
// Gateway install opens an elevated terminal — user needs to complete it there
|
||||
// Don't auto-refresh; user clicks Refresh when done
|
||||
} catch (e) {
|
||||
setError(`Failed to install Gateway Service: ${e}`);
|
||||
} finally {
|
||||
setInstalling(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleOpenUrl = async (url: string) => {
|
||||
try {
|
||||
await open(url);
|
||||
@@ -179,6 +195,16 @@ export function SystemInfo() {
|
||||
installAction: handleInstallOpenclaw,
|
||||
canAutoInstall: true,
|
||||
},
|
||||
...(envStatus.openclaw_installed ? [{
|
||||
id: 'gateway',
|
||||
name: 'Gateway Service',
|
||||
description: 'System service (requires admin)',
|
||||
icon: <Server size={18} />,
|
||||
installed: envStatus.gateway_service_installed,
|
||||
version: null,
|
||||
installAction: handleInstallGateway,
|
||||
canAutoInstall: true,
|
||||
}] : []),
|
||||
];
|
||||
|
||||
const installedCount = requirements.filter(r => r.installed).length;
|
||||
|
||||
Reference in New Issue
Block a user