mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
fix: stabilize daemon service lifecycle and align Agent Status UI (#6)
* Update Welcome page to integrate OAuth provider and adjust branding - Added OAuthProviderButton to the Welcome component for Google authentication. - Updated the branding from AlphaHuman to OpenHuman in the FeaturesStep component. - Removed unnecessary Lottie animation from the Onboarding page for a cleaner layout. - Changed the backend URL to point to the new API endpoint for TinyHumans. * Refactor routing and sidebar components for improved clarity - Commented out the Mnemonic route and related logic in AppRoutes for future consideration. - Updated MiniSidebar to comment out the Invite Friends section. - Removed unused navigation logic and upgrade call-to-action from the Home page. - Adjusted Onboarding navigation to redirect to Home instead of Mnemonic. * Comment out unused settings menu items for future consideration in SettingsHome component * Add core process and RPC functionality for Alphahuman - Introduced new binaries: `alphahuman-core` and `alphahuman-cli` for core process management and command-line interaction. - Implemented `CoreProcessHandle` for managing the lifecycle of the core process. - Added `core_rpc` module for handling RPC requests and responses. - Created `core_server` module to manage core server logic and routing. - Updated `alphahuman` commands to utilize the new RPC structure for health checks, security policies, and configuration management. - Refactored existing code to streamline interactions with the core process and improve overall architecture. * Update dependencies and enhance CLI functionality - Added `clap` for command-line argument parsing in the `alphahuman-cli`. - Updated `Cargo.lock` and `Cargo.toml` to include `clap` and its features. - Refactored `alphahuman-cli` to utilize structured command handling with subcommands. - Improved core process management and logging in `core_process.rs`. - Enhanced routing and error handling in `core_server.rs` with new root and not found handlers. - Updated various commands in `alphahuman.rs` to ensure core process is running before executing RPC calls. * Enhance service management and CLI functionality - Added new functions for daemon program arguments and command line construction in the service module. - Updated macOS and Linux installation functions to dynamically generate program arguments and command lines. - Introduced a new `Reinstall` command in the CLI for easier service management. - Refactored service command handling to utilize local service functions for improved clarity and maintainability. * Refactor daemon references to agent in components and hooks - Updated terminology from "Daemon" to "Agent" in MiniSidebar, DaemonHealthPanel, and TauriCommandsPanel for consistency. - Modified useDaemonHealth hook to probe agent status and handle agent lifecycle management. - Introduced new agent server status interface and function in Tauri commands for improved status checking. - Adjusted related comments and error handling to reflect the changes in terminology. * Refactor Home component and enhance macOS service management - Updated Home component to navigate to in-app conversations instead of opening a Telegram bot link. - Improved macOS service management by implementing modern lifecycle commands and adding compatibility fallbacks for service control. - Introduced new utility functions for handling macOS GUI domain and service targets. * Refactor Home component to enhance navigation - Removed SkillsGrid component and replaced it with a button that navigates to the Skills page. - Improved layout with additional margin for better spacing in the Home component.
This commit is contained in:
Generated
+48
-8
@@ -17,6 +17,7 @@ dependencies = [
|
||||
"chacha20poly1305",
|
||||
"chrono",
|
||||
"chrono-tz",
|
||||
"clap",
|
||||
"console",
|
||||
"cron",
|
||||
"dialoguer",
|
||||
@@ -1138,6 +1139,46 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.57"
|
||||
@@ -2177,7 +2218,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6349,7 +6390,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"socket2",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7016,7 +7057,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.15",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7029,7 +7070,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.11.0",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7585,7 +7626,7 @@ version = "3.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b1fdf65dd6331831494dd616b30351c38e96e45921a27745cf98490458b90bb"
|
||||
dependencies = [
|
||||
"dirs 4.0.0",
|
||||
"dirs 6.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7736,7 +7777,6 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"psm",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
@@ -8312,7 +8352,7 @@ dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
"once_cell",
|
||||
"rustix 1.1.3",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9941,7 +9981,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -23,6 +23,14 @@ path = "src/main.rs"
|
||||
name = "alphahuman-tools-discovery"
|
||||
path = "src/bin/alphahuman-tools-discovery.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "alphahuman-core"
|
||||
path = "src/bin/alphahuman-core.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "alphahuman-cli"
|
||||
path = "src/bin/alphahuman-cli.rs"
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2", features = [] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
@@ -119,6 +127,7 @@ rustls = { version = "0.23", features = ["ring"] }
|
||||
rustls-pki-types = "1.14.0"
|
||||
tokio-rustls = "0.26.4"
|
||||
webpki-roots = "1.0.6"
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
lettre = { version = "0.11.19", default-features = false, features = ["builder", "smtp-transport", "rustls-tls"] }
|
||||
mail-parser = "0.11.2"
|
||||
async-imap = { version = "0.11", features = ["runtime-tokio"], default-features = false }
|
||||
|
||||
@@ -5,7 +5,7 @@ use anyhow::{Context, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
const SERVICE_LABEL: &str = "com.alphahuman.daemon";
|
||||
const LEGACY_SERVICE_LABEL: &str = "com.alphahuman.app";
|
||||
@@ -15,6 +15,30 @@ fn windows_task_name() -> &'static str {
|
||||
WINDOWS_TASK_NAME
|
||||
}
|
||||
|
||||
fn daemon_program_args(exe: &std::path::Path) -> Vec<String> {
|
||||
let file_name = exe
|
||||
.file_name()
|
||||
.and_then(|n| n.to_str())
|
||||
.unwrap_or_default()
|
||||
.to_ascii_lowercase();
|
||||
|
||||
if file_name.contains("alphahuman-core") {
|
||||
vec!["serve".to_string()]
|
||||
} else {
|
||||
vec!["core".to_string(), "serve".to_string()]
|
||||
}
|
||||
}
|
||||
|
||||
fn daemon_command_line(exe: &std::path::Path) -> String {
|
||||
let args = daemon_program_args(exe);
|
||||
let exe_quoted = format!("\"{}\"", exe.display());
|
||||
if args.is_empty() {
|
||||
exe_quoted
|
||||
} else {
|
||||
format!("{} {}", exe_quoted, args.join(" "))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum ServiceState {
|
||||
Running,
|
||||
@@ -49,19 +73,39 @@ pub fn install(config: &Config) -> Result<ServiceStatus> {
|
||||
pub fn start(config: &Config) -> Result<ServiceStatus> {
|
||||
if cfg!(target_os = "macos") {
|
||||
let plist = macos_service_file()?;
|
||||
let domain = macos_gui_domain()?;
|
||||
let primary_target = macos_target(SERVICE_LABEL)?;
|
||||
|
||||
// Check if service is already loaded to avoid "Input/output error"
|
||||
// Prefer modern launchctl lifecycle commands on macOS.
|
||||
if !is_service_loaded_macos()? {
|
||||
log::info!("[service] Loading macOS LaunchAgent service");
|
||||
run_checked(Command::new("launchctl").arg("load").arg("-w").arg(&plist))?;
|
||||
let bootstrap_ok = run_checked(
|
||||
Command::new("launchctl")
|
||||
.arg("bootstrap")
|
||||
.arg(&domain)
|
||||
.arg(&plist),
|
||||
);
|
||||
if let Err(err) = bootstrap_ok {
|
||||
log::warn!(
|
||||
"[service] launchctl bootstrap failed, falling back to load -w: {err}"
|
||||
);
|
||||
run_checked(Command::new("launchctl").arg("load").arg("-w").arg(&plist))?;
|
||||
}
|
||||
} else {
|
||||
log::info!("[service] LaunchAgent service already loaded, skipping load step");
|
||||
}
|
||||
|
||||
// Always try to start - this is safe even if already running
|
||||
log::info!("[service] Starting macOS LaunchAgent service");
|
||||
let start_result = run_checked(Command::new("launchctl").arg("start").arg(SERVICE_LABEL));
|
||||
let start_result = run_checked(
|
||||
Command::new("launchctl")
|
||||
.arg("kickstart")
|
||||
.arg("-k")
|
||||
.arg(&primary_target),
|
||||
);
|
||||
if let Err(e) = start_result {
|
||||
log::warn!("[service] launchctl kickstart failed, trying launchctl start");
|
||||
let _ = run_checked(Command::new("launchctl").arg("start").arg(SERVICE_LABEL));
|
||||
// Check if it's already running - that's not an error for us
|
||||
let status_check = status(config)?;
|
||||
if matches!(status_check.state, ServiceState::Running) {
|
||||
@@ -77,7 +121,11 @@ pub fn start(config: &Config) -> Result<ServiceStatus> {
|
||||
// Check if service is enabled before trying to start
|
||||
if !is_service_enabled_linux()? {
|
||||
log::info!("[service] Enabling systemd service");
|
||||
let _ = run_checked(Command::new("systemctl").args(["--user", "enable", "alphahuman.service"]));
|
||||
let _ = run_checked(Command::new("systemctl").args([
|
||||
"--user",
|
||||
"enable",
|
||||
"alphahuman.service",
|
||||
]));
|
||||
} else {
|
||||
log::info!("[service] Systemd service already enabled");
|
||||
}
|
||||
@@ -86,7 +134,8 @@ pub fn start(config: &Config) -> Result<ServiceStatus> {
|
||||
|
||||
// Try to start - systemctl start is idempotent
|
||||
log::info!("[service] Starting systemd service");
|
||||
let start_result = run_checked(Command::new("systemctl").args(["--user", "start", "alphahuman.service"]));
|
||||
let start_result =
|
||||
run_checked(Command::new("systemctl").args(["--user", "start", "alphahuman.service"]));
|
||||
if let Err(e) = start_result {
|
||||
// Check if it's already active - that's success for us
|
||||
let status_check = status(config)?;
|
||||
@@ -134,17 +183,47 @@ pub fn start(config: &Config) -> Result<ServiceStatus> {
|
||||
pub fn stop(config: &Config) -> Result<ServiceStatus> {
|
||||
if cfg!(target_os = "macos") {
|
||||
let plist = macos_service_file()?;
|
||||
let _ = run_checked(Command::new("launchctl").arg("stop").arg(SERVICE_LABEL));
|
||||
let _ = run_checked(Command::new("launchctl").arg("unload").arg("-w").arg(&plist));
|
||||
let domain = macos_gui_domain()?;
|
||||
let primary_target = macos_target(SERVICE_LABEL)?;
|
||||
|
||||
let legacy_plist = macos_service_file_for(LEGACY_SERVICE_LABEL)?;
|
||||
let _ = run_checked(Command::new("launchctl").arg("stop").arg(LEGACY_SERVICE_LABEL));
|
||||
let _ = run_checked(Command::new("launchctl").arg("unload").arg("-w").arg(&legacy_plist));
|
||||
let legacy_target = macos_target(LEGACY_SERVICE_LABEL)?;
|
||||
|
||||
// Modern lifecycle path first.
|
||||
run_best_effort(
|
||||
Command::new("launchctl")
|
||||
.arg("bootout")
|
||||
.arg(&domain)
|
||||
.arg(&primary_target),
|
||||
);
|
||||
run_best_effort(
|
||||
Command::new("launchctl")
|
||||
.arg("bootout")
|
||||
.arg(&domain)
|
||||
.arg(&plist),
|
||||
);
|
||||
run_best_effort(
|
||||
Command::new("launchctl")
|
||||
.arg("bootout")
|
||||
.arg(&domain)
|
||||
.arg(&legacy_target),
|
||||
);
|
||||
run_best_effort(
|
||||
Command::new("launchctl")
|
||||
.arg("bootout")
|
||||
.arg(&domain)
|
||||
.arg(&legacy_plist),
|
||||
);
|
||||
|
||||
// Compatibility fallback.
|
||||
run_best_effort(Command::new("launchctl").arg("stop").arg(SERVICE_LABEL));
|
||||
run_best_effort(Command::new("launchctl").arg("stop").arg(LEGACY_SERVICE_LABEL));
|
||||
return status(config);
|
||||
}
|
||||
|
||||
if cfg!(target_os = "linux") {
|
||||
let _ = run_checked(Command::new("systemctl").args(["--user", "stop", "alphahuman.service"]));
|
||||
let _ =
|
||||
run_checked(Command::new("systemctl").args(["--user", "stop", "alphahuman.service"]));
|
||||
return status(config);
|
||||
}
|
||||
|
||||
@@ -161,9 +240,9 @@ pub fn stop(config: &Config) -> Result<ServiceStatus> {
|
||||
pub fn status(config: &Config) -> Result<ServiceStatus> {
|
||||
if cfg!(target_os = "macos") {
|
||||
let out = run_capture(Command::new("launchctl").arg("list"))?;
|
||||
let running = out.lines().any(|line| {
|
||||
line.contains(SERVICE_LABEL) || line.contains(LEGACY_SERVICE_LABEL)
|
||||
});
|
||||
let running = out
|
||||
.lines()
|
||||
.any(|line| line.contains(SERVICE_LABEL) || line.contains(LEGACY_SERVICE_LABEL));
|
||||
return Ok(ServiceStatus {
|
||||
state: if running {
|
||||
ServiceState::Running
|
||||
@@ -199,7 +278,8 @@ pub fn status(config: &Config) -> Result<ServiceStatus> {
|
||||
if cfg!(target_os = "windows") {
|
||||
let _ = config;
|
||||
let task_name = windows_task_name();
|
||||
let out = run_capture(Command::new("schtasks").args(["/Query", "/TN", task_name, "/FO", "LIST"]));
|
||||
let out =
|
||||
run_capture(Command::new("schtasks").args(["/Query", "/TN", task_name, "/FO", "LIST"]));
|
||||
match out {
|
||||
Ok(text) => {
|
||||
let running = text.contains("Running");
|
||||
@@ -304,6 +384,11 @@ fn install_macos(config: &Config) -> Result<()> {
|
||||
|
||||
let stdout = logs_dir.join("daemon.stdout.log");
|
||||
let stderr = logs_dir.join("daemon.stderr.log");
|
||||
let daemon_args = daemon_program_args(&exe);
|
||||
let program_args_xml = std::iter::once(exe.display().to_string())
|
||||
.chain(daemon_args)
|
||||
.map(|arg| format!(" <string>{}</string>\n", xml_escape(&arg)))
|
||||
.collect::<String>();
|
||||
|
||||
let plist = format!(
|
||||
r#"<?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
||||
@@ -314,9 +399,7 @@ fn install_macos(config: &Config) -> Result<()> {
|
||||
<string>{label}</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>{exe}</string>
|
||||
<string>daemon</string>
|
||||
</array>
|
||||
{program_args} </array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
@@ -330,13 +413,25 @@ fn install_macos(config: &Config) -> Result<()> {
|
||||
<key>ALPHAHUMAN_DAEMON_INTERNAL</key>
|
||||
<string>false</string>
|
||||
</dict>
|
||||
<key>WorkingDirectory</key>
|
||||
<string>{workdir}</string>
|
||||
<key>ProcessType</key>
|
||||
<string>Background</string>
|
||||
</dict>
|
||||
</plist>
|
||||
"#,
|
||||
label = SERVICE_LABEL,
|
||||
exe = xml_escape(&exe.display().to_string()),
|
||||
program_args = program_args_xml,
|
||||
stdout = xml_escape(&stdout.display().to_string()),
|
||||
stderr = xml_escape(&stderr.display().to_string())
|
||||
stderr = xml_escape(&stderr.display().to_string()),
|
||||
workdir = xml_escape(
|
||||
&config
|
||||
.config_path
|
||||
.parent()
|
||||
.map_or_else(|| PathBuf::from("."), PathBuf::from)
|
||||
.display()
|
||||
.to_string(),
|
||||
)
|
||||
);
|
||||
|
||||
fs::write(&file, plist)?;
|
||||
@@ -359,10 +454,11 @@ fn install_linux(config: &Config) -> Result<()> {
|
||||
|
||||
let stdout = logs_dir.join("daemon.stdout.log");
|
||||
let stderr = logs_dir.join("daemon.stderr.log");
|
||||
let exec_start = daemon_command_line(&exe);
|
||||
|
||||
let unit = format!(
|
||||
"[Unit]\nDescription=Alphahuman Daemon\n\n[Service]\nExecStart={} daemon\nRestart=always\nRestartSec=3\n\nStandardOutput=append:{}\nStandardError=append:{}\n\n[Install]\nWantedBy=default.target\n",
|
||||
exe.display(),
|
||||
"[Unit]\nDescription=Alphahuman Daemon\n\n[Service]\nExecStart={}\nRestart=always\nRestartSec=3\n\nStandardOutput=append:{}\nStandardError=append:{}\n\n[Install]\nWantedBy=default.target\n",
|
||||
exec_start,
|
||||
stdout.display(),
|
||||
stderr.display(),
|
||||
);
|
||||
@@ -384,10 +480,11 @@ fn install_windows(config: &Config) -> Result<()> {
|
||||
let wrapper = logs_dir.join("alphahuman-daemon.cmd");
|
||||
let stdout = logs_dir.join("daemon.stdout.log");
|
||||
let stderr = logs_dir.join("daemon.stderr.log");
|
||||
let daemon_cmd = daemon_command_line(&exe);
|
||||
|
||||
let cmd = format!(
|
||||
"@echo off\n\"{}\" daemon >> \"{}\" 2>> \"{}\"\n",
|
||||
exe.display(),
|
||||
"@echo off\n{} >> \"{}\" 2>> \"{}\"\n",
|
||||
daemon_cmd,
|
||||
stdout.display(),
|
||||
stderr.display()
|
||||
);
|
||||
@@ -428,6 +525,15 @@ fn macos_service_file_for(label: &str) -> Result<PathBuf> {
|
||||
.join(format!("{label}.plist")))
|
||||
}
|
||||
|
||||
fn macos_gui_domain() -> Result<String> {
|
||||
let uid = run_capture(Command::new("id").arg("-u"))?;
|
||||
Ok(format!("gui/{}", uid.trim()))
|
||||
}
|
||||
|
||||
fn macos_target(label: &str) -> Result<String> {
|
||||
Ok(format!("{}/{}", macos_gui_domain()?, label))
|
||||
}
|
||||
|
||||
fn linux_service_file(config: &Config) -> Result<PathBuf> {
|
||||
let config_dir = config
|
||||
.config_path
|
||||
@@ -458,12 +564,36 @@ fn run_capture(cmd: &mut Command) -> Result<String> {
|
||||
Ok(String::from_utf8_lossy(&output.stdout).to_string())
|
||||
}
|
||||
|
||||
fn run_best_effort(cmd: &mut Command) {
|
||||
match cmd.stdout(Stdio::null()).stderr(Stdio::null()).status() {
|
||||
Ok(status) => {
|
||||
if !status.success() {
|
||||
log::debug!("[service] best-effort command failed with status {status}");
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::debug!("[service] best-effort command failed to execute: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the macOS LaunchAgent service is loaded (regardless of running state)
|
||||
fn is_service_loaded_macos() -> Result<bool> {
|
||||
let out = run_capture(Command::new("launchctl").arg("list"))?;
|
||||
Ok(out.lines().any(|line| {
|
||||
line.contains(SERVICE_LABEL) || line.contains(LEGACY_SERVICE_LABEL)
|
||||
}))
|
||||
if run_checked(Command::new("launchctl").arg("print").arg(macos_target(SERVICE_LABEL)?))
|
||||
.is_ok()
|
||||
{
|
||||
return Ok(true);
|
||||
}
|
||||
if run_checked(
|
||||
Command::new("launchctl")
|
||||
.arg("print")
|
||||
.arg(macos_target(LEGACY_SERVICE_LABEL)?),
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
return Ok(true);
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Check if the Linux systemd service is enabled
|
||||
|
||||
@@ -0,0 +1,390 @@
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct RpcRequest {
|
||||
jsonrpc: &'static str,
|
||||
id: u64,
|
||||
method: String,
|
||||
params: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct RpcResponse {
|
||||
result: Option<serde_json::Value>,
|
||||
error: Option<RpcError>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct RpcError {
|
||||
code: i64,
|
||||
message: String,
|
||||
data: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(name = "alphahuman-cli")]
|
||||
#[command(about = "CLI for the AlphaHuman core RPC server")]
|
||||
struct Cli {
|
||||
/// Core RPC endpoint URL
|
||||
#[arg(long, global = true)]
|
||||
rpc_url: Option<String>,
|
||||
|
||||
#[command(subcommand)]
|
||||
command: Command,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
enum Command {
|
||||
/// Check core health
|
||||
Ping,
|
||||
/// Print core version
|
||||
Version,
|
||||
/// Get health snapshot
|
||||
Health,
|
||||
/// Get runtime flags
|
||||
RuntimeFlags,
|
||||
|
||||
/// Config operations
|
||||
Config {
|
||||
#[command(subcommand)]
|
||||
command: ConfigCommand,
|
||||
},
|
||||
|
||||
/// Service operations
|
||||
Service {
|
||||
#[command(subcommand)]
|
||||
command: ServiceCommand,
|
||||
},
|
||||
|
||||
/// Doctor operations
|
||||
Doctor {
|
||||
#[command(subcommand)]
|
||||
command: DoctorCommand,
|
||||
},
|
||||
|
||||
/// Integrations operations
|
||||
Integrations {
|
||||
#[command(subcommand)]
|
||||
command: IntegrationsCommand,
|
||||
},
|
||||
|
||||
/// Send one-shot agent message
|
||||
AgentChat(AgentChatArgs),
|
||||
|
||||
/// Hardware operations
|
||||
Hardware {
|
||||
#[command(subcommand)]
|
||||
command: HardwareCommand,
|
||||
},
|
||||
|
||||
/// Encrypt a secret
|
||||
Encrypt {
|
||||
plaintext: String,
|
||||
},
|
||||
|
||||
/// Decrypt a secret
|
||||
Decrypt {
|
||||
ciphertext: String,
|
||||
},
|
||||
|
||||
/// Toggle browser allow-all runtime flag
|
||||
BrowserAllowAll {
|
||||
#[arg(long)]
|
||||
enabled: bool,
|
||||
},
|
||||
|
||||
/// Refresh model catalog
|
||||
ModelsRefresh {
|
||||
#[arg(long)]
|
||||
provider: Option<String>,
|
||||
#[arg(long, default_value_t = false)]
|
||||
force: bool,
|
||||
},
|
||||
|
||||
/// Migrate OpenClaw memory
|
||||
MigrateOpenclaw {
|
||||
#[arg(long)]
|
||||
source_workspace: Option<String>,
|
||||
#[arg(long, default_value_t = true)]
|
||||
dry_run: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
enum ConfigCommand {
|
||||
/// Get full config snapshot
|
||||
Get,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
enum ServiceCommand {
|
||||
Install,
|
||||
Start,
|
||||
Stop,
|
||||
Status,
|
||||
Reinstall,
|
||||
Uninstall,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
enum DoctorCommand {
|
||||
/// Run doctor checks
|
||||
Report,
|
||||
/// Probe model catalog
|
||||
Models {
|
||||
#[arg(long)]
|
||||
provider: Option<String>,
|
||||
#[arg(long, default_value_t = true)]
|
||||
use_cache: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
enum IntegrationsCommand {
|
||||
/// List integrations
|
||||
List,
|
||||
/// Get one integration info
|
||||
Info {
|
||||
#[arg(long)]
|
||||
name: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
enum HardwareCommand {
|
||||
/// Discover connected hardware
|
||||
Discover,
|
||||
/// Introspect one device path
|
||||
Introspect {
|
||||
#[arg(long)]
|
||||
path: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
struct AgentChatArgs {
|
||||
message: String,
|
||||
#[arg(long)]
|
||||
provider: Option<String>,
|
||||
#[arg(long)]
|
||||
model: Option<String>,
|
||||
#[arg(long)]
|
||||
temperature: Option<f64>,
|
||||
}
|
||||
|
||||
fn endpoint(cli: &Cli) -> String {
|
||||
if let Some(url) = &cli.rpc_url {
|
||||
return url.clone();
|
||||
}
|
||||
std::env::var("ALPHAHUMAN_CORE_RPC_URL")
|
||||
.unwrap_or_else(|_| "http://127.0.0.1:7788/rpc".to_string())
|
||||
}
|
||||
|
||||
fn call(url: &str, method: &str, params: serde_json::Value) -> Result<serde_json::Value, String> {
|
||||
let req = RpcRequest {
|
||||
jsonrpc: "2.0",
|
||||
id: 1,
|
||||
method: method.to_string(),
|
||||
params,
|
||||
};
|
||||
|
||||
let client = reqwest::blocking::Client::new();
|
||||
let resp = client
|
||||
.post(url)
|
||||
.json(&req)
|
||||
.send()
|
||||
.map_err(|e| format!("request failed: {e}"))?;
|
||||
|
||||
let payload: RpcResponse = resp.json().map_err(|e| format!("invalid response: {e}"))?;
|
||||
|
||||
if let Some(err) = payload.error {
|
||||
return Err(format!(
|
||||
"rpc error {}: {}{}",
|
||||
err.code,
|
||||
err.message,
|
||||
err.data.map(|d| format!(" ({d})")).unwrap_or_default()
|
||||
));
|
||||
}
|
||||
|
||||
Ok(payload.result.unwrap_or(serde_json::Value::Null))
|
||||
}
|
||||
|
||||
fn execute(cli: Cli) -> Result<serde_json::Value, String> {
|
||||
let url = endpoint(&cli);
|
||||
|
||||
match cli.command {
|
||||
Command::Ping => call(&url, "core.ping", serde_json::json!({})),
|
||||
Command::Version => call(&url, "core.version", serde_json::json!({})),
|
||||
Command::Health => call(&url, "alphahuman.health_snapshot", serde_json::json!({})),
|
||||
Command::RuntimeFlags => call(&url, "alphahuman.get_runtime_flags", serde_json::json!({})),
|
||||
Command::Config { command } => match command {
|
||||
ConfigCommand::Get => call(&url, "alphahuman.get_config", serde_json::json!({})),
|
||||
},
|
||||
Command::Service { command } => match command {
|
||||
ServiceCommand::Install => local_service_install(),
|
||||
ServiceCommand::Start => local_service_start(),
|
||||
ServiceCommand::Stop => local_service_stop(),
|
||||
ServiceCommand::Status => local_service_status(),
|
||||
ServiceCommand::Reinstall => {
|
||||
let _ = local_service_uninstall()?;
|
||||
local_service_install()
|
||||
}
|
||||
ServiceCommand::Uninstall => local_service_uninstall(),
|
||||
},
|
||||
Command::Doctor { command } => match command {
|
||||
DoctorCommand::Report => call(&url, "alphahuman.doctor_report", serde_json::json!({})),
|
||||
DoctorCommand::Models {
|
||||
provider,
|
||||
use_cache,
|
||||
} => call(
|
||||
&url,
|
||||
"alphahuman.doctor_models",
|
||||
serde_json::json!({
|
||||
"provider_override": provider,
|
||||
"use_cache": use_cache,
|
||||
}),
|
||||
),
|
||||
},
|
||||
Command::Integrations { command } => match command {
|
||||
IntegrationsCommand::List => {
|
||||
call(&url, "alphahuman.list_integrations", serde_json::json!({}))
|
||||
}
|
||||
IntegrationsCommand::Info { name } => call(
|
||||
&url,
|
||||
"alphahuman.get_integration_info",
|
||||
serde_json::json!({ "name": name }),
|
||||
),
|
||||
},
|
||||
Command::AgentChat(args) => call(
|
||||
&url,
|
||||
"alphahuman.agent_chat",
|
||||
serde_json::json!({
|
||||
"message": args.message,
|
||||
"provider_override": args.provider,
|
||||
"model_override": args.model,
|
||||
"temperature": args.temperature,
|
||||
}),
|
||||
),
|
||||
Command::Hardware { command } => match command {
|
||||
HardwareCommand::Discover => {
|
||||
call(&url, "alphahuman.hardware_discover", serde_json::json!({}))
|
||||
}
|
||||
HardwareCommand::Introspect { path } => call(
|
||||
&url,
|
||||
"alphahuman.hardware_introspect",
|
||||
serde_json::json!({ "path": path }),
|
||||
),
|
||||
},
|
||||
Command::Encrypt { plaintext } => call(
|
||||
&url,
|
||||
"alphahuman.encrypt_secret",
|
||||
serde_json::json!({ "plaintext": plaintext }),
|
||||
),
|
||||
Command::Decrypt { ciphertext } => call(
|
||||
&url,
|
||||
"alphahuman.decrypt_secret",
|
||||
serde_json::json!({ "ciphertext": ciphertext }),
|
||||
),
|
||||
Command::BrowserAllowAll { enabled } => call(
|
||||
&url,
|
||||
"alphahuman.set_browser_allow_all",
|
||||
serde_json::json!({ "enabled": enabled }),
|
||||
),
|
||||
Command::ModelsRefresh { provider, force } => call(
|
||||
&url,
|
||||
"alphahuman.models_refresh",
|
||||
serde_json::json!({
|
||||
"provider_override": provider,
|
||||
"force": force,
|
||||
}),
|
||||
),
|
||||
Command::MigrateOpenclaw {
|
||||
source_workspace,
|
||||
dry_run,
|
||||
} => call(
|
||||
&url,
|
||||
"alphahuman.migrate_openclaw",
|
||||
serde_json::json!({
|
||||
"source_workspace": source_workspace,
|
||||
"dry_run": dry_run,
|
||||
}),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn load_config() -> Result<alphahuman::alphahuman::config::Config, String> {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.map_err(|e| format!("failed to build runtime: {e}"))?;
|
||||
runtime
|
||||
.block_on(alphahuman::alphahuman::config::Config::load_or_init())
|
||||
.map_err(|e| format!("failed to load config: {e}"))
|
||||
}
|
||||
|
||||
fn status_value(status: alphahuman::alphahuman::service::ServiceStatus) -> Result<serde_json::Value, String> {
|
||||
serde_json::to_value(status).map_err(|e| format!("failed to serialize service status: {e}"))
|
||||
}
|
||||
|
||||
fn command_response_json(
|
||||
status: alphahuman::alphahuman::service::ServiceStatus,
|
||||
log: &str,
|
||||
) -> Result<serde_json::Value, String> {
|
||||
Ok(json!({
|
||||
"result": status_value(status)?,
|
||||
"logs": [log],
|
||||
}))
|
||||
}
|
||||
|
||||
fn local_service_install() -> Result<serde_json::Value, String> {
|
||||
let config = load_config()?;
|
||||
let status = alphahuman::alphahuman::service::install(&config)
|
||||
.map_err(|e| format!("service install failed: {e}"))?;
|
||||
command_response_json(status, "service install completed")
|
||||
}
|
||||
|
||||
fn local_service_start() -> Result<serde_json::Value, String> {
|
||||
let config = load_config()?;
|
||||
let status = alphahuman::alphahuman::service::start(&config)
|
||||
.map_err(|e| format!("service start failed: {e}"))?;
|
||||
command_response_json(status, "service start completed")
|
||||
}
|
||||
|
||||
fn local_service_stop() -> Result<serde_json::Value, String> {
|
||||
let config = load_config()?;
|
||||
let status = alphahuman::alphahuman::service::stop(&config)
|
||||
.map_err(|e| format!("service stop failed: {e}"))?;
|
||||
command_response_json(status, "service stop completed")
|
||||
}
|
||||
|
||||
fn local_service_status() -> Result<serde_json::Value, String> {
|
||||
let config = load_config()?;
|
||||
let status = alphahuman::alphahuman::service::status(&config)
|
||||
.map_err(|e| format!("service status failed: {e}"))?;
|
||||
command_response_json(status, "service status fetched")
|
||||
}
|
||||
|
||||
fn local_service_uninstall() -> Result<serde_json::Value, String> {
|
||||
let config = load_config()?;
|
||||
let status = alphahuman::alphahuman::service::uninstall(&config)
|
||||
.map_err(|e| format!("service uninstall failed: {e}"))?;
|
||||
command_response_json(status, "service uninstall completed")
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cli = Cli::parse();
|
||||
match execute(cli) {
|
||||
Ok(value) => {
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&value).unwrap_or_else(|_| "null".to_string())
|
||||
);
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("{err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fn main() {
|
||||
let args: Vec<String> = std::env::args().skip(1).collect();
|
||||
if let Err(err) = alphahuman::core_server::run_from_cli_args(&args) {
|
||||
eprintln!("alphahuman-core failed: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
@@ -1,653 +1,439 @@
|
||||
//! Tauri commands for the alphahuman subsystem.
|
||||
//! Tauri command proxies for the standalone alphahuman core process.
|
||||
|
||||
use crate::alphahuman::config::Config;
|
||||
use crate::alphahuman::health;
|
||||
use crate::alphahuman::security::{SecretStore, SecurityPolicy};
|
||||
use crate::alphahuman::{doctor, hardware, integrations, migration, onboard, service};
|
||||
use crate::core_server::{
|
||||
BrowserSettingsUpdate, CommandResponse, ConfigSnapshot, GatewaySettingsUpdate,
|
||||
MemorySettingsUpdate, ModelSettingsUpdate, RuntimeFlags, RuntimeSettingsUpdate,
|
||||
};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::Manager;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct CommandResponse<T> {
|
||||
pub result: T,
|
||||
pub logs: Vec<String>,
|
||||
const DEFAULT_CORE_RPC_URL: &str = "http://127.0.0.1:7788/rpc";
|
||||
|
||||
fn params_none() -> serde_json::Value {
|
||||
serde_json::json!({})
|
||||
}
|
||||
|
||||
fn command_response<T>(result: T, logs: Vec<String>) -> CommandResponse<T> {
|
||||
CommandResponse { result, logs }
|
||||
async fn ensure_core(app: &tauri::AppHandle) -> Result<(), String> {
|
||||
let core = app
|
||||
.try_state::<crate::core_process::CoreProcessHandle>()
|
||||
.ok_or_else(|| "core process handle is not available".to_string())?;
|
||||
let handle: crate::core_process::CoreProcessHandle = (*core).clone();
|
||||
handle.ensure_running().await
|
||||
}
|
||||
|
||||
async fn call_core<T: DeserializeOwned>(
|
||||
app: &tauri::AppHandle,
|
||||
method: &str,
|
||||
params: serde_json::Value,
|
||||
) -> Result<T, String> {
|
||||
ensure_core(app).await?;
|
||||
crate::core_rpc::call(method, params).await
|
||||
}
|
||||
|
||||
async fn load_config_local() -> Result<crate::alphahuman::config::Config, String> {
|
||||
let timeout_duration = std::time::Duration::from_secs(30);
|
||||
match tokio::time::timeout(
|
||||
timeout_duration,
|
||||
crate::alphahuman::config::Config::load_or_init(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(Ok(config)) => Ok(config),
|
||||
Ok(Err(e)) => Err(e.to_string()),
|
||||
Err(_) => Err("Config loading timed out".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AgentServerStatus {
|
||||
pub running: bool,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
/// Return the current health snapshot as JSON.
|
||||
#[tauri::command]
|
||||
pub fn alphahuman_health_snapshot() -> CommandResponse<serde_json::Value> {
|
||||
log::info!("[alphahuman:cmd] health_snapshot called");
|
||||
let logs = vec!["health_snapshot requested".to_string()];
|
||||
command_response(health::snapshot_json(), logs)
|
||||
pub async fn alphahuman_health_snapshot(
|
||||
app: tauri::AppHandle,
|
||||
) -> Result<CommandResponse<serde_json::Value>, String> {
|
||||
call_core(&app, "alphahuman.health_snapshot", params_none()).await
|
||||
}
|
||||
|
||||
/// Return the default security policy info (autonomy config summary).
|
||||
#[tauri::command]
|
||||
pub fn alphahuman_security_policy_info() -> CommandResponse<serde_json::Value> {
|
||||
log::info!("[alphahuman:cmd] security_policy_info called");
|
||||
let policy = SecurityPolicy::default();
|
||||
let payload = serde_json::json!({
|
||||
"autonomy": policy.autonomy,
|
||||
"workspace_only": policy.workspace_only,
|
||||
"allowed_commands": policy.allowed_commands,
|
||||
"max_actions_per_hour": policy.max_actions_per_hour,
|
||||
"require_approval_for_medium_risk": policy.require_approval_for_medium_risk,
|
||||
"block_high_risk_commands": policy.block_high_risk_commands,
|
||||
});
|
||||
let logs = vec!["security_policy_info computed".to_string()];
|
||||
command_response(payload, logs)
|
||||
pub async fn alphahuman_security_policy_info(
|
||||
app: tauri::AppHandle,
|
||||
) -> Result<CommandResponse<serde_json::Value>, String> {
|
||||
call_core(&app, "alphahuman.security_policy_info", params_none()).await
|
||||
}
|
||||
|
||||
/// Encrypt a secret using the alphahuman SecretStore.
|
||||
#[tauri::command]
|
||||
pub fn alphahuman_encrypt_secret(
|
||||
pub async fn alphahuman_encrypt_secret(
|
||||
app: tauri::AppHandle,
|
||||
plaintext: String,
|
||||
) -> Result<CommandResponse<String>, String> {
|
||||
log::info!("[alphahuman:cmd] encrypt_secret called");
|
||||
let data_dir = app
|
||||
.path()
|
||||
.app_data_dir()
|
||||
.map_err(|e| e.to_string())?
|
||||
.join("alphahuman");
|
||||
let store = SecretStore::new(&data_dir, true);
|
||||
store.encrypt(&plaintext).map(|ciphertext| {
|
||||
command_response(ciphertext, vec!["secret encrypted".to_string()])
|
||||
}).map_err(|e| {
|
||||
log::error!("[alphahuman:cmd] encrypt_secret failed: {}", e);
|
||||
e.to_string()
|
||||
})
|
||||
call_core(
|
||||
&app,
|
||||
"alphahuman.encrypt_secret",
|
||||
serde_json::json!({ "plaintext": plaintext }),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Decrypt a secret using the alphahuman SecretStore.
|
||||
#[tauri::command]
|
||||
pub fn alphahuman_decrypt_secret(
|
||||
pub async fn alphahuman_decrypt_secret(
|
||||
app: tauri::AppHandle,
|
||||
ciphertext: String,
|
||||
) -> Result<CommandResponse<String>, String> {
|
||||
log::info!("[alphahuman:cmd] decrypt_secret called");
|
||||
let data_dir = app
|
||||
.path()
|
||||
.app_data_dir()
|
||||
.map_err(|e| e.to_string())?
|
||||
.join("alphahuman");
|
||||
let store = SecretStore::new(&data_dir, true);
|
||||
store.decrypt(&ciphertext).map(|plaintext| {
|
||||
command_response(plaintext, vec!["secret decrypted".to_string()])
|
||||
}).map_err(|e| {
|
||||
log::error!("[alphahuman:cmd] decrypt_secret failed: {}", e);
|
||||
e.to_string()
|
||||
})
|
||||
}
|
||||
|
||||
async fn load_alphahuman_config() -> Result<Config, String> {
|
||||
log::info!("[alphahuman:cmd] load_config called");
|
||||
|
||||
// Add timeout protection and proper error handling to prevent runtime panics
|
||||
let timeout_duration = std::time::Duration::from_secs(30);
|
||||
|
||||
match tokio::time::timeout(timeout_duration, Config::load_or_init()).await {
|
||||
Ok(Ok(config)) => {
|
||||
log::info!("[alphahuman:cmd] config loaded successfully");
|
||||
Ok(config)
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
log::error!("[alphahuman:cmd] load config failed: {}", e);
|
||||
Err(e.to_string())
|
||||
}
|
||||
Err(_) => {
|
||||
log::error!("[alphahuman:cmd] load config timed out after 30 seconds");
|
||||
Err("Config loading timed out".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct ConfigSnapshot {
|
||||
pub config: serde_json::Value,
|
||||
pub workspace_dir: String,
|
||||
pub config_path: String,
|
||||
}
|
||||
|
||||
fn snapshot_config(config: &Config) -> Result<ConfigSnapshot, String> {
|
||||
let value = serde_json::to_value(config).map_err(|e| e.to_string())?;
|
||||
Ok(ConfigSnapshot {
|
||||
config: value,
|
||||
workspace_dir: config.workspace_dir.display().to_string(),
|
||||
config_path: config.config_path.display().to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct ModelSettingsUpdate {
|
||||
pub api_key: Option<String>,
|
||||
pub api_url: Option<String>,
|
||||
pub default_provider: Option<String>,
|
||||
pub default_model: Option<String>,
|
||||
pub default_temperature: Option<f64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct MemorySettingsUpdate {
|
||||
pub backend: Option<String>,
|
||||
pub auto_save: Option<bool>,
|
||||
pub embedding_provider: Option<String>,
|
||||
pub embedding_model: Option<String>,
|
||||
pub embedding_dimensions: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct GatewaySettingsUpdate {
|
||||
pub host: Option<String>,
|
||||
pub port: Option<u16>,
|
||||
pub require_pairing: Option<bool>,
|
||||
pub allow_public_bind: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct RuntimeSettingsUpdate {
|
||||
pub kind: Option<String>,
|
||||
pub reasoning_enabled: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct BrowserSettingsUpdate {
|
||||
pub enabled: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct RuntimeFlags {
|
||||
pub browser_allow_all: bool,
|
||||
pub log_prompts: bool,
|
||||
}
|
||||
|
||||
fn env_flag_enabled(key: &str) -> bool {
|
||||
matches!(
|
||||
std::env::var(key).ok().as_deref(),
|
||||
Some("1") | Some("true") | Some("TRUE") | Some("yes") | Some("YES")
|
||||
call_core(
|
||||
&app,
|
||||
"alphahuman.decrypt_secret",
|
||||
serde_json::json!({ "ciphertext": ciphertext }),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Return the full Alphahuman config snapshot for UI editing.
|
||||
#[tauri::command]
|
||||
pub async fn alphahuman_get_config() -> Result<CommandResponse<ConfigSnapshot>, String> {
|
||||
log::info!("[alphahuman:cmd] get_config called");
|
||||
let config = load_alphahuman_config().await?;
|
||||
let snapshot = snapshot_config(&config)?;
|
||||
Ok(command_response(
|
||||
snapshot,
|
||||
vec![format!(
|
||||
"config loaded from {}",
|
||||
config.config_path.display()
|
||||
)],
|
||||
))
|
||||
pub async fn alphahuman_get_config(
|
||||
app: tauri::AppHandle,
|
||||
) -> Result<CommandResponse<ConfigSnapshot>, String> {
|
||||
call_core(&app, "alphahuman.get_config", params_none()).await
|
||||
}
|
||||
|
||||
/// Update model/provider settings.
|
||||
#[tauri::command]
|
||||
pub async fn alphahuman_update_model_settings(
|
||||
app: tauri::AppHandle,
|
||||
update: ModelSettingsUpdate,
|
||||
) -> Result<CommandResponse<ConfigSnapshot>, String> {
|
||||
log::info!("[alphahuman:cmd] update_model_settings called");
|
||||
let mut config = load_alphahuman_config().await?;
|
||||
log::info!(
|
||||
"[alphahuman:cmd] update_model_settings target config: {}",
|
||||
config.config_path.display()
|
||||
);
|
||||
let ModelSettingsUpdate {
|
||||
api_key,
|
||||
api_url,
|
||||
default_provider,
|
||||
default_model,
|
||||
default_temperature,
|
||||
} = update;
|
||||
log::info!(
|
||||
"[alphahuman:cmd] update_model_settings apply: api_key={}, api_url={}, default_provider={}, default_model={}, default_temperature={}",
|
||||
api_key.as_ref().map(|v| !v.trim().is_empty()).unwrap_or(false),
|
||||
api_url.as_ref().map(|v| !v.trim().is_empty()).unwrap_or(false),
|
||||
default_provider.as_ref().map(|v| !v.trim().is_empty()).unwrap_or(false),
|
||||
default_model.as_ref().map(|v| !v.trim().is_empty()).unwrap_or(false),
|
||||
default_temperature.is_some()
|
||||
);
|
||||
if let Some(api_key) = api_key {
|
||||
config.api_key = if api_key.trim().is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(api_key)
|
||||
};
|
||||
}
|
||||
if let Some(api_url) = api_url {
|
||||
config.api_url = if api_url.trim().is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(api_url)
|
||||
};
|
||||
}
|
||||
if let Some(provider) = default_provider {
|
||||
config.default_provider = if provider.trim().is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(provider)
|
||||
};
|
||||
}
|
||||
if let Some(model) = default_model {
|
||||
config.default_model = if model.trim().is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(model)
|
||||
};
|
||||
}
|
||||
if let Some(temp) = default_temperature {
|
||||
config.default_temperature = temp;
|
||||
}
|
||||
config.save().await.map_err(|e| {
|
||||
log::error!("[alphahuman:cmd] update_model_settings save failed: {}", e);
|
||||
e.to_string()
|
||||
})?;
|
||||
log::info!("[alphahuman:cmd] update_model_settings saved");
|
||||
let snapshot = snapshot_config(&config)?;
|
||||
Ok(command_response(
|
||||
snapshot,
|
||||
vec![format!(
|
||||
"model settings saved to {}",
|
||||
config.config_path.display()
|
||||
)],
|
||||
))
|
||||
call_core(
|
||||
&app,
|
||||
"alphahuman.update_model_settings",
|
||||
serde_json::json!(update),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Update memory settings.
|
||||
#[tauri::command]
|
||||
pub async fn alphahuman_update_memory_settings(
|
||||
app: tauri::AppHandle,
|
||||
update: MemorySettingsUpdate,
|
||||
) -> Result<CommandResponse<ConfigSnapshot>, String> {
|
||||
log::info!("[alphahuman:cmd] update_memory_settings called");
|
||||
let mut config = load_alphahuman_config().await?;
|
||||
if let Some(backend) = update.backend {
|
||||
config.memory.backend = backend;
|
||||
}
|
||||
if let Some(auto_save) = update.auto_save {
|
||||
config.memory.auto_save = auto_save;
|
||||
}
|
||||
if let Some(provider) = update.embedding_provider {
|
||||
config.memory.embedding_provider = provider;
|
||||
}
|
||||
if let Some(model) = update.embedding_model {
|
||||
config.memory.embedding_model = model;
|
||||
}
|
||||
if let Some(dimensions) = update.embedding_dimensions {
|
||||
config.memory.embedding_dimensions = dimensions;
|
||||
}
|
||||
config.save().await.map_err(|e| {
|
||||
log::error!("[alphahuman:cmd] update_memory_settings save failed: {}", e);
|
||||
e.to_string()
|
||||
})?;
|
||||
log::info!("[alphahuman:cmd] update_memory_settings saved");
|
||||
let snapshot = snapshot_config(&config)?;
|
||||
Ok(command_response(
|
||||
snapshot,
|
||||
vec![format!(
|
||||
"memory settings saved to {}",
|
||||
config.config_path.display()
|
||||
)],
|
||||
))
|
||||
call_core(
|
||||
&app,
|
||||
"alphahuman.update_memory_settings",
|
||||
serde_json::json!(update),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Update gateway settings.
|
||||
#[tauri::command]
|
||||
pub async fn alphahuman_update_gateway_settings(
|
||||
app: tauri::AppHandle,
|
||||
update: GatewaySettingsUpdate,
|
||||
) -> Result<CommandResponse<ConfigSnapshot>, String> {
|
||||
log::info!("[alphahuman:cmd] update_gateway_settings called");
|
||||
let mut config = load_alphahuman_config().await?;
|
||||
if let Some(host) = update.host {
|
||||
config.gateway.host = host;
|
||||
}
|
||||
if let Some(port) = update.port {
|
||||
config.gateway.port = port;
|
||||
}
|
||||
if let Some(require_pairing) = update.require_pairing {
|
||||
config.gateway.require_pairing = require_pairing;
|
||||
}
|
||||
if let Some(allow_public_bind) = update.allow_public_bind {
|
||||
config.gateway.allow_public_bind = allow_public_bind;
|
||||
}
|
||||
config.save().await.map_err(|e| {
|
||||
log::error!("[alphahuman:cmd] update_gateway_settings save failed: {}", e);
|
||||
e.to_string()
|
||||
})?;
|
||||
log::info!("[alphahuman:cmd] update_gateway_settings saved");
|
||||
let snapshot = snapshot_config(&config)?;
|
||||
Ok(command_response(
|
||||
snapshot,
|
||||
vec![format!(
|
||||
"gateway settings saved to {}",
|
||||
config.config_path.display()
|
||||
)],
|
||||
))
|
||||
call_core(
|
||||
&app,
|
||||
"alphahuman.update_gateway_settings",
|
||||
serde_json::json!(update),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Update tunnel settings (full tunnel config).
|
||||
#[tauri::command]
|
||||
pub async fn alphahuman_update_tunnel_settings(
|
||||
app: tauri::AppHandle,
|
||||
tunnel: crate::alphahuman::config::TunnelConfig,
|
||||
) -> Result<CommandResponse<ConfigSnapshot>, String> {
|
||||
log::info!("[alphahuman:cmd] update_tunnel_settings called");
|
||||
let mut config = load_alphahuman_config().await?;
|
||||
config.tunnel = tunnel;
|
||||
config.save().await.map_err(|e| {
|
||||
log::error!("[alphahuman:cmd] update_tunnel_settings save failed: {}", e);
|
||||
e.to_string()
|
||||
})?;
|
||||
log::info!("[alphahuman:cmd] update_tunnel_settings saved");
|
||||
let snapshot = snapshot_config(&config)?;
|
||||
Ok(command_response(
|
||||
snapshot,
|
||||
vec![format!(
|
||||
"tunnel settings saved to {}",
|
||||
config.config_path.display()
|
||||
)],
|
||||
))
|
||||
call_core(
|
||||
&app,
|
||||
"alphahuman.update_tunnel_settings",
|
||||
serde_json::json!(tunnel),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Update runtime settings (skill execution backend).
|
||||
#[tauri::command]
|
||||
pub async fn alphahuman_update_runtime_settings(
|
||||
app: tauri::AppHandle,
|
||||
update: RuntimeSettingsUpdate,
|
||||
) -> Result<CommandResponse<ConfigSnapshot>, String> {
|
||||
log::info!("[alphahuman:cmd] update_runtime_settings called");
|
||||
let mut config = load_alphahuman_config().await?;
|
||||
if let Some(kind) = update.kind {
|
||||
config.runtime.kind = kind;
|
||||
}
|
||||
if let Some(reasoning_enabled) = update.reasoning_enabled {
|
||||
config.runtime.reasoning_enabled = Some(reasoning_enabled);
|
||||
}
|
||||
config.save().await.map_err(|e| {
|
||||
log::error!("[alphahuman:cmd] update_runtime_settings save failed: {}", e);
|
||||
e.to_string()
|
||||
})?;
|
||||
log::info!("[alphahuman:cmd] update_runtime_settings saved");
|
||||
let snapshot = snapshot_config(&config)?;
|
||||
Ok(command_response(
|
||||
snapshot,
|
||||
vec![format!(
|
||||
"runtime settings saved to {}",
|
||||
config.config_path.display()
|
||||
)],
|
||||
))
|
||||
call_core(
|
||||
&app,
|
||||
"alphahuman.update_runtime_settings",
|
||||
serde_json::json!(update),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Update browser settings (Chrome/Chromium tool).
|
||||
#[tauri::command]
|
||||
pub async fn alphahuman_update_browser_settings(
|
||||
app: tauri::AppHandle,
|
||||
update: BrowserSettingsUpdate,
|
||||
) -> Result<CommandResponse<ConfigSnapshot>, String> {
|
||||
log::info!("[alphahuman:cmd] update_browser_settings called");
|
||||
log::debug!("[alphahuman:cmd] update_browser_settings requested");
|
||||
let mut config = load_alphahuman_config().await?;
|
||||
if let Some(enabled) = update.enabled {
|
||||
config.browser.enabled = enabled;
|
||||
}
|
||||
config.save().await.map_err(|e| {
|
||||
log::error!("[alphahuman:cmd] update_browser_settings save failed: {}", e);
|
||||
e.to_string()
|
||||
})?;
|
||||
log::info!("[alphahuman:cmd] update_browser_settings saved");
|
||||
let snapshot = snapshot_config(&config)?;
|
||||
Ok(command_response(
|
||||
snapshot,
|
||||
vec![format!(
|
||||
"browser settings saved to {}",
|
||||
config.config_path.display()
|
||||
)],
|
||||
))
|
||||
call_core(
|
||||
&app,
|
||||
"alphahuman.update_browser_settings",
|
||||
serde_json::json!(update),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Read runtime flags that are controlled via environment variables.
|
||||
#[tauri::command]
|
||||
pub fn alphahuman_get_runtime_flags() -> CommandResponse<RuntimeFlags> {
|
||||
log::info!("[alphahuman:cmd] get_runtime_flags called");
|
||||
let flags = RuntimeFlags {
|
||||
browser_allow_all: env_flag_enabled("ALPHAHUMAN_BROWSER_ALLOW_ALL"),
|
||||
log_prompts: env_flag_enabled("ALPHAHUMAN_LOG_PROMPTS"),
|
||||
};
|
||||
command_response(flags, vec!["runtime flags read".to_string()])
|
||||
pub async fn alphahuman_get_runtime_flags(
|
||||
app: tauri::AppHandle,
|
||||
) -> Result<CommandResponse<RuntimeFlags>, String> {
|
||||
call_core(&app, "alphahuman.get_runtime_flags", params_none()).await
|
||||
}
|
||||
|
||||
/// Set browser allow-all flag for the current process.
|
||||
#[tauri::command]
|
||||
pub fn alphahuman_set_browser_allow_all(
|
||||
pub async fn alphahuman_set_browser_allow_all(
|
||||
app: tauri::AppHandle,
|
||||
enabled: bool,
|
||||
) -> CommandResponse<RuntimeFlags> {
|
||||
log::info!("[alphahuman:cmd] set_browser_allow_all called");
|
||||
if enabled {
|
||||
std::env::set_var("ALPHAHUMAN_BROWSER_ALLOW_ALL", "1");
|
||||
} else {
|
||||
std::env::remove_var("ALPHAHUMAN_BROWSER_ALLOW_ALL");
|
||||
}
|
||||
let flags = RuntimeFlags {
|
||||
browser_allow_all: env_flag_enabled("ALPHAHUMAN_BROWSER_ALLOW_ALL"),
|
||||
log_prompts: env_flag_enabled("ALPHAHUMAN_LOG_PROMPTS"),
|
||||
};
|
||||
command_response(flags, vec!["browser allow-all flag updated".to_string()])
|
||||
) -> Result<CommandResponse<RuntimeFlags>, String> {
|
||||
call_core(
|
||||
&app,
|
||||
"alphahuman.set_browser_allow_all",
|
||||
serde_json::json!({ "enabled": enabled }),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Send a single message to the Alphahuman agent and return the response text.
|
||||
#[tauri::command]
|
||||
pub async fn alphahuman_agent_chat(
|
||||
app: tauri::AppHandle,
|
||||
message: String,
|
||||
provider_override: Option<String>,
|
||||
model_override: Option<String>,
|
||||
temperature: Option<f64>,
|
||||
) -> Result<CommandResponse<String>, String> {
|
||||
log::info!("[alphahuman:cmd] agent_chat called");
|
||||
let mut config = load_alphahuman_config().await?;
|
||||
if let Some(provider) = provider_override {
|
||||
config.default_provider = Some(provider);
|
||||
}
|
||||
if let Some(model) = model_override {
|
||||
config.default_model = Some(model);
|
||||
}
|
||||
if let Some(temp) = temperature {
|
||||
config.default_temperature = temp;
|
||||
}
|
||||
let mut agent = crate::alphahuman::agent::Agent::from_config(&config)
|
||||
.map_err(|e| {
|
||||
log::error!("[alphahuman:cmd] agent_chat build failed: {}", e);
|
||||
e.to_string()
|
||||
})?;
|
||||
let response = agent.run_single(&message).await.map_err(|e| {
|
||||
log::error!("[alphahuman:cmd] agent_chat run failed: {}", e);
|
||||
e.to_string()
|
||||
})?;
|
||||
Ok(command_response(
|
||||
response,
|
||||
vec!["agent chat completed".to_string()],
|
||||
))
|
||||
call_core(
|
||||
&app,
|
||||
"alphahuman.agent_chat",
|
||||
serde_json::json!({
|
||||
"message": message,
|
||||
"provider_override": provider_override,
|
||||
"model_override": model_override,
|
||||
"temperature": temperature,
|
||||
}),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Run Alphahuman doctor checks and return a structured report.
|
||||
#[tauri::command]
|
||||
pub async fn alphahuman_doctor_report() -> Result<CommandResponse<doctor::DoctorReport>, String> {
|
||||
log::info!("[alphahuman:cmd] doctor_report called");
|
||||
let config = load_alphahuman_config().await?;
|
||||
doctor::run(&config).map(|report| {
|
||||
command_response(report, vec!["doctor report generated".to_string()])
|
||||
}).map_err(|e| e.to_string())
|
||||
pub async fn alphahuman_doctor_report(
|
||||
app: tauri::AppHandle,
|
||||
) -> Result<CommandResponse<doctor::DoctorReport>, String> {
|
||||
call_core(&app, "alphahuman.doctor_report", params_none()).await
|
||||
}
|
||||
|
||||
/// Run model catalog probes for providers.
|
||||
#[tauri::command]
|
||||
pub async fn alphahuman_doctor_models(
|
||||
app: tauri::AppHandle,
|
||||
provider_override: Option<String>,
|
||||
use_cache: Option<bool>,
|
||||
) -> Result<CommandResponse<doctor::ModelProbeReport>, String> {
|
||||
log::info!("[alphahuman:cmd] doctor_models called");
|
||||
let config = load_alphahuman_config().await?;
|
||||
let use_cache = use_cache.unwrap_or(true);
|
||||
doctor::run_models(&config, provider_override.as_deref(), use_cache)
|
||||
.map(|report| command_response(report, vec!["model probes completed".to_string()]))
|
||||
.map_err(|e| e.to_string())
|
||||
call_core(
|
||||
&app,
|
||||
"alphahuman.doctor_models",
|
||||
serde_json::json!({
|
||||
"provider_override": provider_override,
|
||||
"use_cache": use_cache,
|
||||
}),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// List integrations with status for the current config.
|
||||
#[tauri::command]
|
||||
pub async fn alphahuman_list_integrations(
|
||||
app: tauri::AppHandle,
|
||||
) -> Result<CommandResponse<Vec<integrations::IntegrationInfo>>, String> {
|
||||
log::info!("[alphahuman:cmd] list_integrations called");
|
||||
let config = load_alphahuman_config().await?;
|
||||
Ok(command_response(
|
||||
integrations::list_integrations(&config),
|
||||
vec!["integrations listed".to_string()],
|
||||
))
|
||||
call_core(&app, "alphahuman.list_integrations", params_none()).await
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct IntegrationInfoParams {
|
||||
name: String,
|
||||
}
|
||||
|
||||
/// Get details for a single integration.
|
||||
#[tauri::command]
|
||||
pub async fn alphahuman_get_integration_info(
|
||||
app: tauri::AppHandle,
|
||||
name: String,
|
||||
) -> Result<CommandResponse<integrations::IntegrationInfo>, String> {
|
||||
log::info!("[alphahuman:cmd] get_integration_info called");
|
||||
let config = load_alphahuman_config().await?;
|
||||
integrations::get_integration_info(&config, &name)
|
||||
.map(|info| command_response(info, vec![format!("integration loaded: {name}")]))
|
||||
.map_err(|e| {
|
||||
log::error!("[alphahuman:cmd] get_integration_info failed: {}", e);
|
||||
e.to_string()
|
||||
})
|
||||
let params = IntegrationInfoParams { name };
|
||||
call_core(
|
||||
&app,
|
||||
"alphahuman.get_integration_info",
|
||||
serde_json::json!(params),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Refresh the model catalog for a provider (or default provider).
|
||||
#[tauri::command]
|
||||
pub async fn alphahuman_models_refresh(
|
||||
app: tauri::AppHandle,
|
||||
provider_override: Option<String>,
|
||||
force: Option<bool>,
|
||||
) -> Result<CommandResponse<onboard::ModelRefreshResult>, String> {
|
||||
log::info!("[alphahuman:cmd] models_refresh called");
|
||||
let config = load_alphahuman_config().await?;
|
||||
let force = force.unwrap_or(false);
|
||||
onboard::run_models_refresh(&config, provider_override.as_deref(), force)
|
||||
.map(|result| command_response(result, vec!["model refresh completed".to_string()]))
|
||||
.map_err(|e| {
|
||||
log::error!("[alphahuman:cmd] models_refresh failed: {}", e);
|
||||
e.to_string()
|
||||
})
|
||||
call_core(
|
||||
&app,
|
||||
"alphahuman.models_refresh",
|
||||
serde_json::json!({
|
||||
"provider_override": provider_override,
|
||||
"force": force,
|
||||
}),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Migrate OpenClaw memory into the current Alphahuman workspace.
|
||||
#[tauri::command]
|
||||
pub async fn alphahuman_migrate_openclaw(
|
||||
app: tauri::AppHandle,
|
||||
source_workspace: Option<String>,
|
||||
dry_run: Option<bool>,
|
||||
) -> Result<CommandResponse<migration::MigrationReport>, String> {
|
||||
log::info!("[alphahuman:cmd] migrate_openclaw called");
|
||||
let config = load_alphahuman_config().await?;
|
||||
let source = source_workspace.map(std::path::PathBuf::from);
|
||||
let dry_run = dry_run.unwrap_or(true);
|
||||
migration::migrate_openclaw_memory(&config, source, dry_run)
|
||||
.await
|
||||
.map(|report| command_response(report, vec!["migration completed".to_string()]))
|
||||
.map_err(|e| {
|
||||
log::error!("[alphahuman:cmd] migrate_openclaw failed: {}", e);
|
||||
e.to_string()
|
||||
})
|
||||
call_core(
|
||||
&app,
|
||||
"alphahuman.migrate_openclaw",
|
||||
serde_json::json!({
|
||||
"source_workspace": source_workspace,
|
||||
"dry_run": dry_run,
|
||||
}),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Discover connected hardware devices (feature-gated).
|
||||
#[tauri::command]
|
||||
pub fn alphahuman_hardware_discover() -> CommandResponse<Vec<hardware::DiscoveredDevice>> {
|
||||
log::info!("[alphahuman:cmd] hardware_discover called");
|
||||
command_response(
|
||||
hardware::discover_hardware(),
|
||||
vec!["hardware discovery complete".to_string()],
|
||||
)
|
||||
pub async fn alphahuman_hardware_discover(
|
||||
app: tauri::AppHandle,
|
||||
) -> Result<CommandResponse<Vec<hardware::DiscoveredDevice>>, String> {
|
||||
call_core(&app, "alphahuman.hardware_discover", params_none()).await
|
||||
}
|
||||
|
||||
/// Introspect a device path (feature-gated).
|
||||
#[tauri::command]
|
||||
pub fn alphahuman_hardware_introspect(
|
||||
pub async fn alphahuman_hardware_introspect(
|
||||
app: tauri::AppHandle,
|
||||
path: String,
|
||||
) -> Result<CommandResponse<hardware::HardwareIntrospect>, String> {
|
||||
log::info!("[alphahuman:cmd] hardware_introspect called");
|
||||
hardware::introspect_device(&path)
|
||||
.map(|info| command_response(info, vec![format!("introspected {path}")]))
|
||||
.map_err(|e| {
|
||||
log::error!("[alphahuman:cmd] hardware_introspect failed: {}", e);
|
||||
e.to_string()
|
||||
call_core(
|
||||
&app,
|
||||
"alphahuman.hardware_introspect",
|
||||
serde_json::json!({ "path": path }),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Return whether the local core agent server is reachable.
|
||||
#[tauri::command]
|
||||
pub async fn alphahuman_agent_server_status() -> Result<CommandResponse<AgentServerStatus>, String> {
|
||||
let url = std::env::var("ALPHAHUMAN_CORE_RPC_URL").unwrap_or_else(|_| DEFAULT_CORE_RPC_URL.to_string());
|
||||
let running = crate::core_rpc::ping().await;
|
||||
Ok(CommandResponse {
|
||||
result: AgentServerStatus { running, url },
|
||||
logs: vec!["agent server status checked".to_string()],
|
||||
})
|
||||
}
|
||||
|
||||
/// Install the Alphahuman daemon service.
|
||||
#[tauri::command]
|
||||
pub async fn alphahuman_service_install(
|
||||
_app: tauri::AppHandle,
|
||||
) -> Result<CommandResponse<service::ServiceStatus>, String> {
|
||||
log::info!("[alphahuman:cmd] service_install called");
|
||||
let config = load_alphahuman_config().await?;
|
||||
let config = load_config_local().await?;
|
||||
service::install(&config)
|
||||
.map(|status| command_response(status, vec!["service install completed".to_string()]))
|
||||
.map_err(|e| {
|
||||
log::error!("[alphahuman:cmd] service_install failed: {}", e);
|
||||
e.to_string()
|
||||
.map(|status| CommandResponse {
|
||||
result: status,
|
||||
logs: vec!["service install completed".to_string()],
|
||||
})
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// Start the Alphahuman daemon service.
|
||||
#[tauri::command]
|
||||
pub async fn alphahuman_service_start() -> Result<CommandResponse<service::ServiceStatus>, String> {
|
||||
log::info!("[alphahuman:cmd] service_start called");
|
||||
let config = load_alphahuman_config().await?;
|
||||
pub async fn alphahuman_service_start(
|
||||
_app: tauri::AppHandle,
|
||||
) -> Result<CommandResponse<service::ServiceStatus>, String> {
|
||||
let config = load_config_local().await?;
|
||||
service::start(&config)
|
||||
.map(|status| {
|
||||
log::info!("[alphahuman:cmd] External daemon service started - health events should be emitted from service process");
|
||||
command_response(status, vec!["service start completed".to_string()])
|
||||
})
|
||||
.map_err(|e| {
|
||||
log::error!("[alphahuman:cmd] service_start failed: {}", e);
|
||||
e.to_string()
|
||||
.map(|status| CommandResponse {
|
||||
result: status,
|
||||
logs: vec!["service start completed".to_string()],
|
||||
})
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// Stop the Alphahuman daemon service.
|
||||
#[tauri::command]
|
||||
pub async fn alphahuman_service_stop() -> Result<CommandResponse<service::ServiceStatus>, String> {
|
||||
log::info!("[alphahuman:cmd] service_stop called");
|
||||
let config = load_alphahuman_config().await?;
|
||||
service::stop(&config)
|
||||
.map(|status| command_response(status, vec!["service stop completed".to_string()]))
|
||||
.map_err(|e| {
|
||||
log::error!("[alphahuman:cmd] service_stop failed: {}", e);
|
||||
e.to_string()
|
||||
})
|
||||
pub async fn alphahuman_service_stop(
|
||||
app: tauri::AppHandle,
|
||||
) -> Result<CommandResponse<service::ServiceStatus>, String> {
|
||||
let config = load_config_local().await?;
|
||||
let status = service::stop(&config)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
// Also stop any locally managed core process in this Tauri app.
|
||||
if let Some(core) = app.try_state::<crate::core_process::CoreProcessHandle>() {
|
||||
let core_handle: crate::core_process::CoreProcessHandle = (*core).clone();
|
||||
core_handle.shutdown().await;
|
||||
}
|
||||
|
||||
Ok(CommandResponse {
|
||||
result: status,
|
||||
logs: vec!["service stop completed".to_string()],
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the Alphahuman daemon service status.
|
||||
#[tauri::command]
|
||||
pub async fn alphahuman_service_status(
|
||||
_app: tauri::AppHandle,
|
||||
) -> Result<CommandResponse<service::ServiceStatus>, String> {
|
||||
log::info!("[alphahuman:cmd] service_status called");
|
||||
let config = load_alphahuman_config().await?;
|
||||
let config = load_config_local().await?;
|
||||
service::status(&config)
|
||||
.map(|status| command_response(status, vec!["service status fetched".to_string()]))
|
||||
.map_err(|e| {
|
||||
log::error!("[alphahuman:cmd] service_status failed: {}", e);
|
||||
e.to_string()
|
||||
.map(|status| CommandResponse {
|
||||
result: status,
|
||||
logs: vec!["service status fetched".to_string()],
|
||||
})
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// Uninstall the Alphahuman daemon service.
|
||||
#[tauri::command]
|
||||
pub async fn alphahuman_service_uninstall(
|
||||
app: tauri::AppHandle,
|
||||
) -> Result<CommandResponse<service::ServiceStatus>, String> {
|
||||
log::info!("[alphahuman:cmd] service_uninstall called");
|
||||
let config = load_alphahuman_config().await?;
|
||||
service::uninstall(&config)
|
||||
.map(|status| command_response(status, vec!["service uninstall completed".to_string()]))
|
||||
.map_err(|e| {
|
||||
log::error!("[alphahuman:cmd] service_uninstall failed: {}", e);
|
||||
e.to_string()
|
||||
})
|
||||
let config = load_config_local().await?;
|
||||
let status = service::uninstall(&config)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
if let Some(core) = app.try_state::<crate::core_process::CoreProcessHandle>() {
|
||||
let core_handle: crate::core_process::CoreProcessHandle = (*core).clone();
|
||||
core_handle.shutdown().await;
|
||||
}
|
||||
|
||||
Ok(CommandResponse {
|
||||
result: status,
|
||||
logs: vec!["service uninstall completed".to_string()],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::process::{Child, Command};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CoreProcessHandle {
|
||||
child: Arc<Mutex<Option<Child>>>,
|
||||
port: u16,
|
||||
}
|
||||
|
||||
impl CoreProcessHandle {
|
||||
pub fn new(port: u16) -> Self {
|
||||
Self {
|
||||
child: Arc::new(Mutex::new(None)),
|
||||
port,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rpc_url(&self) -> String {
|
||||
format!("http://127.0.0.1:{}/rpc", self.port)
|
||||
}
|
||||
|
||||
pub async fn ensure_running(&self) -> Result<(), String> {
|
||||
if crate::core_rpc::ping().await {
|
||||
log::info!("[core] found existing core rpc endpoint at {}", self.rpc_url());
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut guard = self.child.lock().await;
|
||||
if guard.is_none() {
|
||||
let exe = std::env::current_exe()
|
||||
.map_err(|e| format!("failed to resolve current executable: {e}"))?;
|
||||
|
||||
let mut cmd = Command::new(exe);
|
||||
cmd.arg("core")
|
||||
.arg("serve")
|
||||
.arg("--port")
|
||||
.arg(self.port.to_string());
|
||||
|
||||
log::info!("[core] spawning core process: {:?} core serve --port {}", cmd.as_std().get_program(), self.port);
|
||||
|
||||
let child = cmd
|
||||
.spawn()
|
||||
.map_err(|e| format!("failed to spawn core process: {e}"))?;
|
||||
|
||||
*guard = Some(child);
|
||||
}
|
||||
drop(guard);
|
||||
|
||||
for _ in 0..40 {
|
||||
if crate::core_rpc::ping().await {
|
||||
log::info!("[core] core rpc became ready at {}", self.rpc_url());
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut guard = self.child.lock().await;
|
||||
if let Some(child) = guard.as_mut() {
|
||||
match child.try_wait() {
|
||||
Ok(Some(status)) => {
|
||||
return Err(format!("core process exited before ready: {status}"));
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(e) => return Err(format!("failed checking core process status: {e}")),
|
||||
}
|
||||
}
|
||||
drop(guard);
|
||||
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||
}
|
||||
|
||||
Err("core process did not become ready".to_string())
|
||||
}
|
||||
|
||||
pub async fn shutdown(&self) {
|
||||
let mut guard = self.child.lock().await;
|
||||
if let Some(child) = guard.as_mut() {
|
||||
let _ = child.kill().await;
|
||||
}
|
||||
*guard = None;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default_core_port() -> u16 {
|
||||
std::env::var("ALPHAHUMAN_CORE_PORT")
|
||||
.ok()
|
||||
.and_then(|v| v.parse::<u16>().ok())
|
||||
.unwrap_or(7788)
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const DEFAULT_CORE_RPC_URL: &str = "http://127.0.0.1:7788/rpc";
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct RpcRequest<'a> {
|
||||
jsonrpc: &'static str,
|
||||
id: u64,
|
||||
method: &'a str,
|
||||
params: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct RpcResponse<T> {
|
||||
#[allow(dead_code)]
|
||||
jsonrpc: String,
|
||||
#[allow(dead_code)]
|
||||
id: serde_json::Value,
|
||||
result: Option<T>,
|
||||
error: Option<RpcError>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct RpcError {
|
||||
#[allow(dead_code)]
|
||||
code: i64,
|
||||
message: String,
|
||||
data: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
fn rpc_url() -> String {
|
||||
std::env::var("ALPHAHUMAN_CORE_RPC_URL").unwrap_or_else(|_| DEFAULT_CORE_RPC_URL.to_string())
|
||||
}
|
||||
|
||||
pub async fn call<T: DeserializeOwned>(
|
||||
method: &str,
|
||||
params: serde_json::Value,
|
||||
) -> Result<T, String> {
|
||||
let client = reqwest::Client::new();
|
||||
let req = RpcRequest {
|
||||
jsonrpc: "2.0",
|
||||
id: 1,
|
||||
method,
|
||||
params,
|
||||
};
|
||||
|
||||
let resp = client
|
||||
.post(rpc_url())
|
||||
.json(&req)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("core rpc request failed: {e}"))?;
|
||||
|
||||
let status = resp.status();
|
||||
if !status.is_success() {
|
||||
let text = resp.text().await.unwrap_or_default();
|
||||
return Err(format!("core rpc http error {status}: {text}"));
|
||||
}
|
||||
|
||||
let payload: RpcResponse<T> = resp
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| format!("core rpc decode failed: {e}"))?;
|
||||
|
||||
if let Some(err) = payload.error {
|
||||
let data = err.data.map(|d| format!(" ({d})")).unwrap_or_default();
|
||||
return Err(format!("{}{}", err.message, data));
|
||||
}
|
||||
|
||||
payload
|
||||
.result
|
||||
.ok_or_else(|| "core rpc missing result".to_string())
|
||||
}
|
||||
|
||||
pub async fn ping() -> bool {
|
||||
#[derive(Deserialize)]
|
||||
struct Pong {
|
||||
ok: bool,
|
||||
}
|
||||
|
||||
match call::<Pong>("core.ping", serde_json::json!({})).await {
|
||||
Ok(p) => p.ok,
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,728 @@
|
||||
use anyhow::Result;
|
||||
use axum::extract::State;
|
||||
use axum::http::StatusCode;
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use axum::routing::{get, post};
|
||||
use axum::{Json, Router};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
|
||||
use crate::alphahuman::config::Config;
|
||||
use crate::alphahuman::health;
|
||||
use crate::alphahuman::security::{SecretStore, SecurityPolicy};
|
||||
use crate::alphahuman::{doctor, hardware, integrations, migration, onboard, service};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CommandResponse<T> {
|
||||
pub result: T,
|
||||
pub logs: Vec<String>,
|
||||
}
|
||||
|
||||
fn command_response<T>(result: T, logs: Vec<String>) -> CommandResponse<T> {
|
||||
CommandResponse { result, logs }
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct RpcRequest {
|
||||
#[allow(dead_code)]
|
||||
jsonrpc: String,
|
||||
id: serde_json::Value,
|
||||
method: String,
|
||||
#[serde(default)]
|
||||
params: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct RpcSuccess {
|
||||
jsonrpc: &'static str,
|
||||
id: serde_json::Value,
|
||||
result: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct RpcFailure {
|
||||
jsonrpc: &'static str,
|
||||
id: serde_json::Value,
|
||||
error: RpcError,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct RpcError {
|
||||
code: i64,
|
||||
message: String,
|
||||
data: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AppState {
|
||||
core_version: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ConfigSnapshot {
|
||||
pub config: serde_json::Value,
|
||||
pub workspace_dir: String,
|
||||
pub config_path: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ModelSettingsUpdate {
|
||||
pub api_key: Option<String>,
|
||||
pub api_url: Option<String>,
|
||||
pub default_provider: Option<String>,
|
||||
pub default_model: Option<String>,
|
||||
pub default_temperature: Option<f64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct MemorySettingsUpdate {
|
||||
pub backend: Option<String>,
|
||||
pub auto_save: Option<bool>,
|
||||
pub embedding_provider: Option<String>,
|
||||
pub embedding_model: Option<String>,
|
||||
pub embedding_dimensions: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct GatewaySettingsUpdate {
|
||||
pub host: Option<String>,
|
||||
pub port: Option<u16>,
|
||||
pub require_pairing: Option<bool>,
|
||||
pub allow_public_bind: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RuntimeSettingsUpdate {
|
||||
pub kind: Option<String>,
|
||||
pub reasoning_enabled: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct BrowserSettingsUpdate {
|
||||
pub enabled: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RuntimeFlags {
|
||||
pub browser_allow_all: bool,
|
||||
pub log_prompts: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AgentChatParams {
|
||||
message: String,
|
||||
provider_override: Option<String>,
|
||||
model_override: Option<String>,
|
||||
temperature: Option<f64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct DoctorModelsParams {
|
||||
provider_override: Option<String>,
|
||||
use_cache: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct IntegrationInfoParams {
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ModelsRefreshParams {
|
||||
provider_override: Option<String>,
|
||||
force: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct MigrateOpenClawParams {
|
||||
source_workspace: Option<String>,
|
||||
dry_run: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct HardwareIntrospectParams {
|
||||
path: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct EncryptSecretParams {
|
||||
plaintext: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct DecryptSecretParams {
|
||||
ciphertext: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct SetBrowserAllowAllParams {
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
async fn load_alphahuman_config() -> Result<Config, String> {
|
||||
let timeout_duration = std::time::Duration::from_secs(30);
|
||||
match tokio::time::timeout(timeout_duration, Config::load_or_init()).await {
|
||||
Ok(Ok(config)) => Ok(config),
|
||||
Ok(Err(e)) => Err(e.to_string()),
|
||||
Err(_) => Err("Config loading timed out".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn snapshot_config(config: &Config) -> Result<ConfigSnapshot, String> {
|
||||
let value = serde_json::to_value(config).map_err(|e| e.to_string())?;
|
||||
Ok(ConfigSnapshot {
|
||||
config: value,
|
||||
workspace_dir: config.workspace_dir.display().to_string(),
|
||||
config_path: config.config_path.display().to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
fn env_flag_enabled(key: &str) -> bool {
|
||||
matches!(
|
||||
std::env::var(key).ok().as_deref(),
|
||||
Some("1") | Some("true") | Some("TRUE") | Some("yes") | Some("YES")
|
||||
)
|
||||
}
|
||||
|
||||
fn secret_store_for_config(config: &Config) -> SecretStore {
|
||||
let data_dir = config
|
||||
.config_path
|
||||
.parent()
|
||||
.map_or_else(|| std::path::PathBuf::from("."), std::path::PathBuf::from);
|
||||
SecretStore::new(&data_dir, true)
|
||||
}
|
||||
|
||||
fn parse_params<T: DeserializeOwned>(params: serde_json::Value) -> Result<T, String> {
|
||||
serde_json::from_value(params).map_err(|e| format!("invalid params: {e}"))
|
||||
}
|
||||
|
||||
fn rpc_error_response(id: serde_json::Value, code: i64, message: String) -> Response {
|
||||
(
|
||||
StatusCode::OK,
|
||||
Json(RpcFailure {
|
||||
jsonrpc: "2.0",
|
||||
id,
|
||||
error: RpcError {
|
||||
code,
|
||||
message,
|
||||
data: None,
|
||||
},
|
||||
}),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
|
||||
fn to_rpc_success(id: serde_json::Value, value: serde_json::Value) -> Response {
|
||||
(
|
||||
StatusCode::OK,
|
||||
Json(RpcSuccess {
|
||||
jsonrpc: "2.0",
|
||||
id,
|
||||
result: value,
|
||||
}),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
|
||||
fn to_json_value<T: Serialize>(value: T) -> Result<serde_json::Value, String> {
|
||||
serde_json::to_value(value).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
async fn rpc_handler(State(state): State<AppState>, Json(req): Json<RpcRequest>) -> Response {
|
||||
let id = req.id.clone();
|
||||
|
||||
let result = dispatch(state, req.method.as_str(), req.params).await;
|
||||
|
||||
match result {
|
||||
Ok(value) => to_rpc_success(id, value),
|
||||
Err(message) => rpc_error_response(id, -32000, message),
|
||||
}
|
||||
}
|
||||
|
||||
async fn dispatch(
|
||||
state: AppState,
|
||||
method: &str,
|
||||
params: serde_json::Value,
|
||||
) -> Result<serde_json::Value, String> {
|
||||
match method {
|
||||
"core.ping" => to_json_value(json!({ "ok": true })),
|
||||
"core.version" => to_json_value(json!({ "version": state.core_version })),
|
||||
|
||||
"alphahuman.health_snapshot" => to_json_value(command_response(
|
||||
health::snapshot_json(),
|
||||
vec!["health_snapshot requested".to_string()],
|
||||
)),
|
||||
|
||||
"alphahuman.security_policy_info" => {
|
||||
let policy = SecurityPolicy::default();
|
||||
let payload = json!({
|
||||
"autonomy": policy.autonomy,
|
||||
"workspace_only": policy.workspace_only,
|
||||
"allowed_commands": policy.allowed_commands,
|
||||
"max_actions_per_hour": policy.max_actions_per_hour,
|
||||
"require_approval_for_medium_risk": policy.require_approval_for_medium_risk,
|
||||
"block_high_risk_commands": policy.block_high_risk_commands,
|
||||
});
|
||||
to_json_value(command_response(
|
||||
payload,
|
||||
vec!["security_policy_info computed".to_string()],
|
||||
))
|
||||
}
|
||||
|
||||
"alphahuman.get_config" => {
|
||||
let config = load_alphahuman_config().await?;
|
||||
let snapshot = snapshot_config(&config)?;
|
||||
to_json_value(command_response(
|
||||
snapshot,
|
||||
vec![format!(
|
||||
"config loaded from {}",
|
||||
config.config_path.display()
|
||||
)],
|
||||
))
|
||||
}
|
||||
|
||||
"alphahuman.update_model_settings" => {
|
||||
let update: ModelSettingsUpdate = parse_params(params)?;
|
||||
let mut config = load_alphahuman_config().await?;
|
||||
if let Some(api_key) = update.api_key {
|
||||
config.api_key = if api_key.trim().is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(api_key)
|
||||
};
|
||||
}
|
||||
if let Some(api_url) = update.api_url {
|
||||
config.api_url = if api_url.trim().is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(api_url)
|
||||
};
|
||||
}
|
||||
if let Some(provider) = update.default_provider {
|
||||
config.default_provider = if provider.trim().is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(provider)
|
||||
};
|
||||
}
|
||||
if let Some(model) = update.default_model {
|
||||
config.default_model = if model.trim().is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(model)
|
||||
};
|
||||
}
|
||||
if let Some(temp) = update.default_temperature {
|
||||
config.default_temperature = temp;
|
||||
}
|
||||
config.save().await.map_err(|e| e.to_string())?;
|
||||
let snapshot = snapshot_config(&config)?;
|
||||
to_json_value(command_response(
|
||||
snapshot,
|
||||
vec![format!(
|
||||
"model settings saved to {}",
|
||||
config.config_path.display()
|
||||
)],
|
||||
))
|
||||
}
|
||||
|
||||
"alphahuman.update_memory_settings" => {
|
||||
let update: MemorySettingsUpdate = parse_params(params)?;
|
||||
let mut config = load_alphahuman_config().await?;
|
||||
if let Some(backend) = update.backend {
|
||||
config.memory.backend = backend;
|
||||
}
|
||||
if let Some(auto_save) = update.auto_save {
|
||||
config.memory.auto_save = auto_save;
|
||||
}
|
||||
if let Some(provider) = update.embedding_provider {
|
||||
config.memory.embedding_provider = provider;
|
||||
}
|
||||
if let Some(model) = update.embedding_model {
|
||||
config.memory.embedding_model = model;
|
||||
}
|
||||
if let Some(dimensions) = update.embedding_dimensions {
|
||||
config.memory.embedding_dimensions = dimensions;
|
||||
}
|
||||
config.save().await.map_err(|e| e.to_string())?;
|
||||
let snapshot = snapshot_config(&config)?;
|
||||
to_json_value(command_response(
|
||||
snapshot,
|
||||
vec![format!(
|
||||
"memory settings saved to {}",
|
||||
config.config_path.display()
|
||||
)],
|
||||
))
|
||||
}
|
||||
|
||||
"alphahuman.update_gateway_settings" => {
|
||||
let update: GatewaySettingsUpdate = parse_params(params)?;
|
||||
let mut config = load_alphahuman_config().await?;
|
||||
if let Some(host) = update.host {
|
||||
config.gateway.host = host;
|
||||
}
|
||||
if let Some(port) = update.port {
|
||||
config.gateway.port = port;
|
||||
}
|
||||
if let Some(require_pairing) = update.require_pairing {
|
||||
config.gateway.require_pairing = require_pairing;
|
||||
}
|
||||
if let Some(allow_public_bind) = update.allow_public_bind {
|
||||
config.gateway.allow_public_bind = allow_public_bind;
|
||||
}
|
||||
config.save().await.map_err(|e| e.to_string())?;
|
||||
let snapshot = snapshot_config(&config)?;
|
||||
to_json_value(command_response(
|
||||
snapshot,
|
||||
vec![format!(
|
||||
"gateway settings saved to {}",
|
||||
config.config_path.display()
|
||||
)],
|
||||
))
|
||||
}
|
||||
|
||||
"alphahuman.update_tunnel_settings" => {
|
||||
let tunnel: crate::alphahuman::config::TunnelConfig = parse_params(params)?;
|
||||
let mut config = load_alphahuman_config().await?;
|
||||
config.tunnel = tunnel;
|
||||
config.save().await.map_err(|e| e.to_string())?;
|
||||
let snapshot = snapshot_config(&config)?;
|
||||
to_json_value(command_response(
|
||||
snapshot,
|
||||
vec![format!(
|
||||
"tunnel settings saved to {}",
|
||||
config.config_path.display()
|
||||
)],
|
||||
))
|
||||
}
|
||||
|
||||
"alphahuman.update_runtime_settings" => {
|
||||
let update: RuntimeSettingsUpdate = parse_params(params)?;
|
||||
let mut config = load_alphahuman_config().await?;
|
||||
if let Some(kind) = update.kind {
|
||||
config.runtime.kind = kind;
|
||||
}
|
||||
if let Some(reasoning_enabled) = update.reasoning_enabled {
|
||||
config.runtime.reasoning_enabled = Some(reasoning_enabled);
|
||||
}
|
||||
config.save().await.map_err(|e| e.to_string())?;
|
||||
let snapshot = snapshot_config(&config)?;
|
||||
to_json_value(command_response(
|
||||
snapshot,
|
||||
vec![format!(
|
||||
"runtime settings saved to {}",
|
||||
config.config_path.display()
|
||||
)],
|
||||
))
|
||||
}
|
||||
|
||||
"alphahuman.update_browser_settings" => {
|
||||
let update: BrowserSettingsUpdate = parse_params(params)?;
|
||||
let mut config = load_alphahuman_config().await?;
|
||||
if let Some(enabled) = update.enabled {
|
||||
config.browser.enabled = enabled;
|
||||
}
|
||||
config.save().await.map_err(|e| e.to_string())?;
|
||||
let snapshot = snapshot_config(&config)?;
|
||||
to_json_value(command_response(
|
||||
snapshot,
|
||||
vec![format!(
|
||||
"browser settings saved to {}",
|
||||
config.config_path.display()
|
||||
)],
|
||||
))
|
||||
}
|
||||
|
||||
"alphahuman.get_runtime_flags" => {
|
||||
let flags = RuntimeFlags {
|
||||
browser_allow_all: env_flag_enabled("ALPHAHUMAN_BROWSER_ALLOW_ALL"),
|
||||
log_prompts: env_flag_enabled("ALPHAHUMAN_LOG_PROMPTS"),
|
||||
};
|
||||
to_json_value(command_response(
|
||||
flags,
|
||||
vec!["runtime flags read".to_string()],
|
||||
))
|
||||
}
|
||||
|
||||
"alphahuman.set_browser_allow_all" => {
|
||||
let p: SetBrowserAllowAllParams = parse_params(params)?;
|
||||
if p.enabled {
|
||||
std::env::set_var("ALPHAHUMAN_BROWSER_ALLOW_ALL", "1");
|
||||
} else {
|
||||
std::env::remove_var("ALPHAHUMAN_BROWSER_ALLOW_ALL");
|
||||
}
|
||||
let flags = RuntimeFlags {
|
||||
browser_allow_all: env_flag_enabled("ALPHAHUMAN_BROWSER_ALLOW_ALL"),
|
||||
log_prompts: env_flag_enabled("ALPHAHUMAN_LOG_PROMPTS"),
|
||||
};
|
||||
to_json_value(command_response(
|
||||
flags,
|
||||
vec!["browser allow-all flag updated".to_string()],
|
||||
))
|
||||
}
|
||||
|
||||
"alphahuman.agent_chat" => {
|
||||
let p: AgentChatParams = parse_params(params)?;
|
||||
let mut config = load_alphahuman_config().await?;
|
||||
if let Some(provider) = p.provider_override {
|
||||
config.default_provider = Some(provider);
|
||||
}
|
||||
if let Some(model) = p.model_override {
|
||||
config.default_model = Some(model);
|
||||
}
|
||||
if let Some(temp) = p.temperature {
|
||||
config.default_temperature = temp;
|
||||
}
|
||||
let mut agent =
|
||||
crate::alphahuman::agent::Agent::from_config(&config).map_err(|e| e.to_string())?;
|
||||
let response = agent
|
||||
.run_single(&p.message)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
to_json_value(command_response(
|
||||
response,
|
||||
vec!["agent chat completed".to_string()],
|
||||
))
|
||||
}
|
||||
|
||||
"alphahuman.encrypt_secret" => {
|
||||
let p: EncryptSecretParams = parse_params(params)?;
|
||||
let config = load_alphahuman_config().await?;
|
||||
let store = secret_store_for_config(&config);
|
||||
let ciphertext = store.encrypt(&p.plaintext).map_err(|e| e.to_string())?;
|
||||
to_json_value(command_response(
|
||||
ciphertext,
|
||||
vec!["secret encrypted".to_string()],
|
||||
))
|
||||
}
|
||||
|
||||
"alphahuman.decrypt_secret" => {
|
||||
let p: DecryptSecretParams = parse_params(params)?;
|
||||
let config = load_alphahuman_config().await?;
|
||||
let store = secret_store_for_config(&config);
|
||||
let plaintext = store.decrypt(&p.ciphertext).map_err(|e| e.to_string())?;
|
||||
to_json_value(command_response(
|
||||
plaintext,
|
||||
vec!["secret decrypted".to_string()],
|
||||
))
|
||||
}
|
||||
|
||||
"alphahuman.doctor_report" => {
|
||||
let config = load_alphahuman_config().await?;
|
||||
let report = doctor::run(&config).map_err(|e| e.to_string())?;
|
||||
to_json_value(command_response(
|
||||
report,
|
||||
vec!["doctor report generated".to_string()],
|
||||
))
|
||||
}
|
||||
|
||||
"alphahuman.doctor_models" => {
|
||||
let p: DoctorModelsParams = parse_params(params)?;
|
||||
let config = load_alphahuman_config().await?;
|
||||
let use_cache = p.use_cache.unwrap_or(true);
|
||||
let report = doctor::run_models(&config, p.provider_override.as_deref(), use_cache)
|
||||
.map_err(|e| e.to_string())?;
|
||||
to_json_value(command_response(
|
||||
report,
|
||||
vec!["model probes completed".to_string()],
|
||||
))
|
||||
}
|
||||
|
||||
"alphahuman.list_integrations" => {
|
||||
let config = load_alphahuman_config().await?;
|
||||
to_json_value(command_response(
|
||||
integrations::list_integrations(&config),
|
||||
vec!["integrations listed".to_string()],
|
||||
))
|
||||
}
|
||||
|
||||
"alphahuman.get_integration_info" => {
|
||||
let p: IntegrationInfoParams = parse_params(params)?;
|
||||
let config = load_alphahuman_config().await?;
|
||||
let info =
|
||||
integrations::get_integration_info(&config, &p.name).map_err(|e| e.to_string())?;
|
||||
to_json_value(command_response(
|
||||
info,
|
||||
vec![format!("integration loaded: {}", p.name)],
|
||||
))
|
||||
}
|
||||
|
||||
"alphahuman.models_refresh" => {
|
||||
let p: ModelsRefreshParams = parse_params(params)?;
|
||||
let config = load_alphahuman_config().await?;
|
||||
let result = onboard::run_models_refresh(
|
||||
&config,
|
||||
p.provider_override.as_deref(),
|
||||
p.force.unwrap_or(false),
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
to_json_value(command_response(
|
||||
result,
|
||||
vec!["model refresh completed".to_string()],
|
||||
))
|
||||
}
|
||||
|
||||
"alphahuman.migrate_openclaw" => {
|
||||
let p: MigrateOpenClawParams = parse_params(params)?;
|
||||
let config = load_alphahuman_config().await?;
|
||||
let source = p.source_workspace.map(std::path::PathBuf::from);
|
||||
let report =
|
||||
migration::migrate_openclaw_memory(&config, source, p.dry_run.unwrap_or(true))
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
to_json_value(command_response(
|
||||
report,
|
||||
vec!["migration completed".to_string()],
|
||||
))
|
||||
}
|
||||
|
||||
"alphahuman.hardware_discover" => to_json_value(command_response(
|
||||
hardware::discover_hardware(),
|
||||
vec!["hardware discovery complete".to_string()],
|
||||
)),
|
||||
|
||||
"alphahuman.hardware_introspect" => {
|
||||
let p: HardwareIntrospectParams = parse_params(params)?;
|
||||
let info = hardware::introspect_device(&p.path).map_err(|e| e.to_string())?;
|
||||
to_json_value(command_response(
|
||||
info,
|
||||
vec![format!("introspected {}", p.path)],
|
||||
))
|
||||
}
|
||||
|
||||
"alphahuman.service_install" => {
|
||||
let config = load_alphahuman_config().await?;
|
||||
let status = service::install(&config).map_err(|e| e.to_string())?;
|
||||
to_json_value(command_response(
|
||||
status,
|
||||
vec!["service install completed".to_string()],
|
||||
))
|
||||
}
|
||||
|
||||
"alphahuman.service_start" => {
|
||||
let config = load_alphahuman_config().await?;
|
||||
let status = service::start(&config).map_err(|e| e.to_string())?;
|
||||
to_json_value(command_response(
|
||||
status,
|
||||
vec!["service start completed".to_string()],
|
||||
))
|
||||
}
|
||||
|
||||
"alphahuman.service_stop" => {
|
||||
let config = load_alphahuman_config().await?;
|
||||
let status = service::stop(&config).map_err(|e| e.to_string())?;
|
||||
to_json_value(command_response(
|
||||
status,
|
||||
vec!["service stop completed".to_string()],
|
||||
))
|
||||
}
|
||||
|
||||
"alphahuman.service_status" => {
|
||||
let config = load_alphahuman_config().await?;
|
||||
let status = service::status(&config).map_err(|e| e.to_string())?;
|
||||
to_json_value(command_response(
|
||||
status,
|
||||
vec!["service status fetched".to_string()],
|
||||
))
|
||||
}
|
||||
|
||||
"alphahuman.service_uninstall" => {
|
||||
let config = load_alphahuman_config().await?;
|
||||
let status = service::uninstall(&config).map_err(|e| e.to_string())?;
|
||||
to_json_value(command_response(
|
||||
status,
|
||||
vec!["service uninstall completed".to_string()],
|
||||
))
|
||||
}
|
||||
|
||||
_ => Err(format!("unknown method: {method}")),
|
||||
}
|
||||
}
|
||||
|
||||
async fn health_handler() -> impl IntoResponse {
|
||||
(StatusCode::OK, Json(json!({ "ok": true })))
|
||||
}
|
||||
|
||||
async fn root_handler() -> impl IntoResponse {
|
||||
(
|
||||
StatusCode::OK,
|
||||
Json(json!({
|
||||
"name": "alphahuman-core",
|
||||
"ok": true,
|
||||
"endpoints": {
|
||||
"health": "/health",
|
||||
"rpc": "/rpc"
|
||||
},
|
||||
"usage": {
|
||||
"jsonrpc": {
|
||||
"version": "2.0",
|
||||
"method": "core.ping",
|
||||
"params": {}
|
||||
}
|
||||
}
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
async fn not_found_handler() -> impl IntoResponse {
|
||||
(
|
||||
StatusCode::NOT_FOUND,
|
||||
Json(json!({
|
||||
"ok": false,
|
||||
"error": "not_found",
|
||||
"message": "Route not found. Try /, /health, or /rpc."
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
fn core_port() -> u16 {
|
||||
std::env::var("ALPHAHUMAN_CORE_PORT")
|
||||
.ok()
|
||||
.and_then(|v| v.parse::<u16>().ok())
|
||||
.unwrap_or(7788)
|
||||
}
|
||||
|
||||
pub async fn run_server(port: Option<u16>) -> Result<()> {
|
||||
let port = port.unwrap_or_else(core_port);
|
||||
let bind_addr = format!("127.0.0.1:{port}");
|
||||
let listener = tokio::net::TcpListener::bind(&bind_addr).await?;
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(root_handler))
|
||||
.route("/health", get(health_handler))
|
||||
.route("/rpc", post(rpc_handler))
|
||||
.fallback(not_found_handler)
|
||||
.with_state(AppState {
|
||||
core_version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
});
|
||||
|
||||
log::info!("[core] listening on http://{bind_addr}");
|
||||
axum::serve(listener, app).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_from_cli_args(args: &[String]) -> Result<()> {
|
||||
let mut port: Option<u16> = None;
|
||||
|
||||
let mut idx = 0;
|
||||
while idx < args.len() {
|
||||
match args[idx].as_str() {
|
||||
"serve" => {}
|
||||
"--port" => {
|
||||
let value = args
|
||||
.get(idx + 1)
|
||||
.ok_or_else(|| anyhow::anyhow!("missing value for --port"))?;
|
||||
port = Some(value.parse::<u16>()?);
|
||||
idx += 1;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
idx += 1;
|
||||
}
|
||||
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()?;
|
||||
runtime.block_on(run_server(port))
|
||||
}
|
||||
+70
-17
@@ -9,26 +9,31 @@
|
||||
//! - Native notifications
|
||||
|
||||
mod ai;
|
||||
pub mod alphahuman;
|
||||
mod auth;
|
||||
mod commands;
|
||||
mod core_process;
|
||||
mod core_rpc;
|
||||
pub mod core_server;
|
||||
pub mod memory;
|
||||
mod models;
|
||||
mod runtime;
|
||||
mod services;
|
||||
pub mod alphahuman;
|
||||
mod unified_skills;
|
||||
mod utils;
|
||||
|
||||
use ai::*;
|
||||
use commands::unified_skills::{
|
||||
unified_execute_skill, unified_generate_skill, unified_list_skills,
|
||||
unified_self_evolve_skill,
|
||||
unified_execute_skill, unified_generate_skill, unified_list_skills, unified_self_evolve_skill,
|
||||
};
|
||||
use commands::*;
|
||||
use services::socket_service::SOCKET_SERVICE;
|
||||
use std::path::PathBuf;
|
||||
use tauri::{AppHandle, Manager, RunEvent, Emitter};
|
||||
use tokio::{fs, time::{interval, Duration}};
|
||||
use tauri::{AppHandle, Emitter, Manager, RunEvent};
|
||||
use tokio::{
|
||||
fs,
|
||||
time::{interval, Duration},
|
||||
};
|
||||
|
||||
#[cfg(desktop)]
|
||||
use tauri::{
|
||||
@@ -51,8 +56,8 @@ async fn write_ai_config_file(filename: String, content: String) -> Result<bool,
|
||||
use std::env;
|
||||
|
||||
// Get the project root directory (parent of src-tauri)
|
||||
let current_dir = env::current_dir()
|
||||
.map_err(|e| format!("Failed to get current directory: {e}"))?;
|
||||
let current_dir =
|
||||
env::current_dir().map_err(|e| format!("Failed to get current directory: {e}"))?;
|
||||
|
||||
// Go up one level to get project root (since we're in src-tauri/)
|
||||
let project_root = current_dir
|
||||
@@ -74,8 +79,7 @@ async fn write_ai_config_file(filename: String, content: String) -> Result<bool,
|
||||
let file_path = ai_dir.join(&filename);
|
||||
|
||||
// Ensure ai directory exists
|
||||
std::fs::create_dir_all(&ai_dir)
|
||||
.map_err(|e| format!("Failed to create ai directory: {e}"))?;
|
||||
std::fs::create_dir_all(&ai_dir).map_err(|e| format!("Failed to create ai directory: {e}"))?;
|
||||
|
||||
// Write the file
|
||||
std::fs::write(&file_path, content)
|
||||
@@ -277,7 +281,10 @@ async fn watch_daemon_health_file(app_handle: AppHandle, data_dir: PathBuf) {
|
||||
let mut interval = interval(Duration::from_secs(2));
|
||||
let mut last_modified: Option<std::time::SystemTime> = None;
|
||||
|
||||
log::info!("[alphahuman] Watching daemon health file: {}", state_file.display());
|
||||
log::info!(
|
||||
"[alphahuman] Watching daemon health file: {}",
|
||||
state_file.display()
|
||||
);
|
||||
|
||||
loop {
|
||||
interval.tick().await;
|
||||
@@ -290,26 +297,44 @@ async fn watch_daemon_health_file(app_handle: AppHandle, data_dir: PathBuf) {
|
||||
|
||||
// Read and parse health data
|
||||
if let Ok(content) = fs::read_to_string(&state_file).await {
|
||||
if let Ok(json_value) = serde_json::from_str::<serde_json::Value>(&content) {
|
||||
log::debug!("[alphahuman] Broadcasting health event from file: {:?}", json_value);
|
||||
if let Ok(json_value) = serde_json::from_str::<serde_json::Value>(&content)
|
||||
{
|
||||
log::debug!(
|
||||
"[alphahuman] Broadcasting health event from file: {:?}",
|
||||
json_value
|
||||
);
|
||||
|
||||
// Emit Tauri event to frontend (same as internal daemon)
|
||||
if let Err(e) = app_handle.emit("alphahuman:health", &json_value) {
|
||||
log::error!("[alphahuman] Failed to emit health event from file: {}", e);
|
||||
log::error!(
|
||||
"[alphahuman] Failed to emit health event from file: {}",
|
||||
e
|
||||
);
|
||||
} else {
|
||||
log::debug!("[alphahuman] Health event emitted successfully from file");
|
||||
log::debug!(
|
||||
"[alphahuman] Health event emitted successfully from file"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
log::debug!("[alphahuman] Failed to parse health file as JSON: {}", state_file.display());
|
||||
log::debug!(
|
||||
"[alphahuman] Failed to parse health file as JSON: {}",
|
||||
state_file.display()
|
||||
);
|
||||
}
|
||||
} else {
|
||||
log::debug!("[alphahuman] Failed to read health file: {}", state_file.display());
|
||||
log::debug!(
|
||||
"[alphahuman] Failed to read health file: {}",
|
||||
state_file.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// File doesn't exist yet - external daemon may not be writing yet
|
||||
log::debug!("[alphahuman] Health file not found yet: {}", state_file.display());
|
||||
log::debug!(
|
||||
"[alphahuman] Health file not found yet: {}",
|
||||
state_file.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -638,6 +663,21 @@ pub fn run() {
|
||||
}
|
||||
}
|
||||
|
||||
// Start/ensure standalone core process for business logic RPC.
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
let core_handle = core_process::CoreProcessHandle::new(core_process::default_core_port());
|
||||
std::env::set_var("ALPHAHUMAN_CORE_RPC_URL", core_handle.rpc_url());
|
||||
app.manage(core_handle.clone());
|
||||
tauri::async_runtime::spawn(async move {
|
||||
if let Err(err) = core_handle.ensure_running().await {
|
||||
log::error!("[core] failed to start core process: {err}");
|
||||
} else {
|
||||
log::info!("[core] core process ready");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if daemon_mode {
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let _ = window.hide();
|
||||
@@ -789,6 +829,7 @@ pub fn run() {
|
||||
alphahuman_service_stop,
|
||||
alphahuman_service_status,
|
||||
alphahuman_service_uninstall,
|
||||
alphahuman_agent_server_status,
|
||||
// Unified skill registry commands
|
||||
unified_list_skills,
|
||||
unified_execute_skill,
|
||||
@@ -909,6 +950,7 @@ pub fn run() {
|
||||
alphahuman_service_stop,
|
||||
alphahuman_service_status,
|
||||
alphahuman_service_uninstall,
|
||||
alphahuman_agent_server_status,
|
||||
// Unified skill registry commands (mobile stubs)
|
||||
unified_list_skills,
|
||||
unified_execute_skill,
|
||||
@@ -951,6 +993,13 @@ pub fn run() {
|
||||
log::info!("[alphahuman] Daemon shutdown signalled");
|
||||
}
|
||||
|
||||
if let Some(core) = app_handle.try_state::<core_process::CoreProcessHandle>() {
|
||||
let core_handle: core_process::CoreProcessHandle = (*core).clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
core_handle.shutdown().await;
|
||||
});
|
||||
}
|
||||
|
||||
let _ = app_handle;
|
||||
}
|
||||
|
||||
@@ -960,3 +1009,7 @@ pub fn run() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn run_core_from_args(args: &[String]) -> anyhow::Result<()> {
|
||||
core_server::run_from_cli_args(args)
|
||||
}
|
||||
|
||||
@@ -2,5 +2,14 @@
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
if args.get(1).map(String::as_str) == Some("core") {
|
||||
if let Err(err) = alphahuman::run_core_from_args(&args[2..]) {
|
||||
eprintln!("core process failed: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
alphahuman::run()
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
"deep-link": {
|
||||
"desktop": {
|
||||
"schemes": [
|
||||
"alphahuman"
|
||||
"openhuman"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,21 +67,21 @@ const navItems = [
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'invites',
|
||||
label: 'Invite Friends',
|
||||
path: '/invites',
|
||||
icon: (
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M18 9v3m0 0v3m0-3h3m-3 0h-3m-2-5a4 4 0 11-8 0 4 4 0 018 0zM3 20a6 6 0 0112 0v1H3v-1z"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
// {
|
||||
// id: 'invites',
|
||||
// label: 'Invite Friends',
|
||||
// path: '/invites',
|
||||
// icon: (
|
||||
// <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
// <path
|
||||
// strokeLinecap="round"
|
||||
// strokeLinejoin="round"
|
||||
// strokeWidth={2}
|
||||
// d="M18 9v3m0 0v3m0-3h3m-3 0h-3m-2-5a4 4 0 11-8 0 4 4 0 018 0zM3 20a6 6 0 0112 0v1H3v-1z"
|
||||
// />
|
||||
// </svg>
|
||||
// ),
|
||||
// },
|
||||
{
|
||||
id: 'settings',
|
||||
label: 'Settings',
|
||||
@@ -178,7 +178,7 @@ const MiniSidebar = () => {
|
||||
</div>
|
||||
{/* Tooltip */}
|
||||
<div className="pointer-events-none absolute left-full top-1/2 -translate-y-1/2 ml-2 px-2 py-1 bg-stone-800 text-white text-xs rounded-lg whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity duration-150">
|
||||
Daemon Status
|
||||
Agent Status
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/**
|
||||
* Daemon Health Panel
|
||||
* Agent Health Panel
|
||||
*
|
||||
* Detailed health breakdown component showing daemon status, component health,
|
||||
* and providing manual control buttons for daemon lifecycle management.
|
||||
* Detailed health breakdown component showing agent status, component health,
|
||||
* and providing manual control buttons for agent lifecycle management.
|
||||
*/
|
||||
import {
|
||||
ArrowPathIcon,
|
||||
@@ -29,13 +29,13 @@ const DaemonHealthPanel = ({ userId, onClose, className = '' }: Props) => {
|
||||
const daemonHealth = useDaemonHealth(userId);
|
||||
const [operationLoading, setOperationLoading] = useState<string | null>(null);
|
||||
|
||||
// Handle daemon operations with loading states
|
||||
// Handle agent operations with loading states
|
||||
const handleOperation = async (operation: () => Promise<unknown>, operationName: string) => {
|
||||
setOperationLoading(operationName);
|
||||
try {
|
||||
await operation();
|
||||
} catch (error) {
|
||||
console.error(`[DaemonHealthPanel] ${operationName} failed:`, error);
|
||||
console.error(`[AgentHealthPanel] ${operationName} failed:`, error);
|
||||
} finally {
|
||||
setOperationLoading(null);
|
||||
}
|
||||
@@ -87,7 +87,7 @@ const DaemonHealthPanel = ({ userId, onClose, className = '' }: Props) => {
|
||||
<div className={`bg-stone-900 rounded-lg border border-stone-700 p-6 space-y-6 ${className}`}>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-lg font-semibold text-white">Daemon Health</h3>
|
||||
<h3 className="text-lg font-semibold text-white">Agent Status</h3>
|
||||
{onClose && (
|
||||
<button onClick={onClose} className="text-gray-400 hover:text-white transition-colors">
|
||||
<XMarkIcon className="w-5 h-5" />
|
||||
@@ -172,8 +172,8 @@ const DaemonHealthPanel = ({ userId, onClose, className = '' }: Props) => {
|
||||
{/* Auto-start Toggle */}
|
||||
<div className="flex items-center justify-between p-3 rounded-lg bg-stone-800/40 border border-stone-700/60">
|
||||
<div>
|
||||
<div className="text-sm font-medium text-gray-300">Auto-start Daemon</div>
|
||||
<div className="text-xs text-gray-500">Automatically start daemon on app launch</div>
|
||||
<div className="text-sm font-medium text-gray-300">Auto-start Agent</div>
|
||||
<div className="text-xs text-gray-500">Automatically start agent on app launch</div>
|
||||
</div>
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
|
||||
@@ -116,41 +116,41 @@ const SettingsHome = () => {
|
||||
onClick: () => navigateToSettings('ai'),
|
||||
dangerous: false,
|
||||
},
|
||||
{
|
||||
id: 'agent-chat',
|
||||
title: 'Agent Chat',
|
||||
description: 'Send messages directly to your agent',
|
||||
devOnly: true,
|
||||
icon: (
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M8 10h8m-8 4h5m-6 6l-4 4V6a2 2 0 012-2h12a2 2 0 012 2v9a2 2 0 01-2 2H7z"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
onClick: () => navigateToSettings('agent-chat'),
|
||||
dangerous: false,
|
||||
},
|
||||
{
|
||||
id: 'privacy',
|
||||
title: 'Privacy & Security',
|
||||
description: 'Control your privacy and security settings',
|
||||
icon: (
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
onClick: () => navigateToSettings('privacy'),
|
||||
dangerous: false,
|
||||
},
|
||||
// {
|
||||
// id: 'agent-chat',
|
||||
// title: 'Agent Chat',
|
||||
// description: 'Send messages directly to your agent',
|
||||
// devOnly: true,
|
||||
// icon: (
|
||||
// <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
// <path
|
||||
// strokeLinecap="round"
|
||||
// strokeLinejoin="round"
|
||||
// strokeWidth={2}
|
||||
// d="M8 10h8m-8 4h5m-6 6l-4 4V6a2 2 0 012-2h12a2 2 0 012 2v9a2 2 0 01-2 2H7z"
|
||||
// />
|
||||
// </svg>
|
||||
// ),
|
||||
// onClick: () => navigateToSettings('agent-chat'),
|
||||
// dangerous: false,
|
||||
// },
|
||||
// {
|
||||
// id: 'privacy',
|
||||
// title: 'Privacy & Security',
|
||||
// description: 'Control your privacy and security settings',
|
||||
// icon: (
|
||||
// <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
// <path
|
||||
// strokeLinecap="round"
|
||||
// strokeLinejoin="round"
|
||||
// strokeWidth={2}
|
||||
// d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
|
||||
// />
|
||||
// </svg>
|
||||
// ),
|
||||
// onClick: () => navigateToSettings('privacy'),
|
||||
// dangerous: false,
|
||||
// },
|
||||
// {
|
||||
// id: 'profile',
|
||||
// title: 'Profile',
|
||||
@@ -325,18 +325,20 @@ const SettingsHome = () => {
|
||||
<div className="p-4 space-y-6">
|
||||
{/* Main Settings */}
|
||||
<div>
|
||||
{mainMenuItems.filter(item => !item.devOnly || IS_DEV).map((item, index, filtered) => (
|
||||
<SettingsMenuItem
|
||||
key={item.id}
|
||||
icon={item.icon}
|
||||
title={item.title}
|
||||
description={item.description}
|
||||
onClick={item.onClick}
|
||||
dangerous={item.dangerous}
|
||||
isFirst={index === 0}
|
||||
isLast={index === filtered.length - 1}
|
||||
/>
|
||||
))}
|
||||
{mainMenuItems
|
||||
.filter(item => !item.devOnly || IS_DEV)
|
||||
.map((item, index, filtered) => (
|
||||
<SettingsMenuItem
|
||||
key={item.id}
|
||||
icon={item.icon}
|
||||
title={item.title}
|
||||
description={item.description}
|
||||
onClick={item.onClick}
|
||||
dangerous={item.dangerous}
|
||||
isFirst={index === 0}
|
||||
isLast={index === filtered.length - 1}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Destructive Actions */}
|
||||
|
||||
@@ -974,7 +974,7 @@ const TauriCommandsPanel = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<InputGroup title="Daemon Service Management">
|
||||
<InputGroup title="Agent Service Management">
|
||||
<div className="md:col-span-2">
|
||||
<div className="space-y-4">
|
||||
{/* Live Status Display */}
|
||||
@@ -983,7 +983,7 @@ const TauriCommandsPanel = () => {
|
||||
<DaemonHealthIndicator size="md" />
|
||||
<div>
|
||||
<div className="text-white font-medium">
|
||||
Daemon Status: {daemonHealth.status}
|
||||
Agent Status: {daemonHealth.status}
|
||||
</div>
|
||||
<div className="text-xs text-gray-400">
|
||||
Last update:{' '}
|
||||
@@ -1074,9 +1074,9 @@ const TauriCommandsPanel = () => {
|
||||
{/* Auto-start Toggle */}
|
||||
<div className="flex items-center justify-between p-3 rounded-lg bg-stone-800/40 border border-stone-700/60">
|
||||
<div>
|
||||
<div className="text-sm font-medium text-gray-300">Auto-start Daemon</div>
|
||||
<div className="text-sm font-medium text-gray-300">Auto-start Agent</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
Automatically start daemon on app launch
|
||||
Automatically start agent on app launch
|
||||
</div>
|
||||
</div>
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* React hook for accessing daemon health state and actions.
|
||||
* Provides convenient access to daemon status, components, and control functions.
|
||||
*/
|
||||
import { useCallback } from 'react';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
import {
|
||||
resetConnectionAttempts,
|
||||
@@ -16,10 +16,12 @@ import {
|
||||
selectIsDaemonAutoStartEnabled,
|
||||
selectIsDaemonRecovering,
|
||||
setAutoStartEnabled,
|
||||
setDaemonStatus,
|
||||
setIsRecovering,
|
||||
} from '../store/daemonSlice';
|
||||
import { useAppDispatch, useAppSelector } from '../store/hooks';
|
||||
import {
|
||||
alphahumanAgentServerStatus,
|
||||
alphahumanServiceStart,
|
||||
alphahumanServiceStatus,
|
||||
alphahumanServiceStop,
|
||||
@@ -38,51 +40,90 @@ export const useDaemonHealth = (userId?: string) => {
|
||||
const isAutoStartEnabled = useAppSelector(state => selectIsDaemonAutoStartEnabled(state, userId));
|
||||
const connectionAttempts = useAppSelector(state => selectDaemonConnectionAttempts(state, userId));
|
||||
const isRecovering = useAppSelector(state => selectIsDaemonRecovering(state, userId));
|
||||
const uid = userId || '__pending__';
|
||||
|
||||
const probeAgentStatus = useCallback(async (): Promise<boolean> => {
|
||||
try {
|
||||
const result = await alphahumanAgentServerStatus();
|
||||
const running = !!result?.result?.running;
|
||||
dispatch(setDaemonStatus({ userId: uid, status: running ? 'running' : 'disconnected' }));
|
||||
return running;
|
||||
} catch (error) {
|
||||
console.error('[useDaemonHealth] Failed to probe agent status:', error);
|
||||
dispatch(setDaemonStatus({ userId: uid, status: 'disconnected' }));
|
||||
return false;
|
||||
}
|
||||
}, [dispatch, uid]);
|
||||
|
||||
const waitForAgentStatus = useCallback(
|
||||
async (targetRunning: boolean, timeoutMs = 10000): Promise<boolean> => {
|
||||
const startedAt = Date.now();
|
||||
while (Date.now() - startedAt < timeoutMs) {
|
||||
const running = await probeAgentStatus();
|
||||
if (running === targetRunning) {
|
||||
return true;
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
}
|
||||
return false;
|
||||
},
|
||||
[probeAgentStatus]
|
||||
);
|
||||
|
||||
// Action creators
|
||||
const startDaemon = useCallback(async (): Promise<CommandResponse<ServiceStatus> | null> => {
|
||||
try {
|
||||
dispatch(setDaemonStatus({ userId: uid, status: 'starting' }));
|
||||
const result = await alphahumanServiceStart();
|
||||
// Check if the service status indicates success
|
||||
if (result.result && result.result.state === 'Running') {
|
||||
dispatch(resetConnectionAttempts({ userId: userId || '__pending__' }));
|
||||
const running = await waitForAgentStatus(true);
|
||||
if (running) {
|
||||
if (result?.result) {
|
||||
(result.result as { state?: string }).state = 'Running';
|
||||
}
|
||||
dispatch(resetConnectionAttempts({ userId: uid }));
|
||||
} else {
|
||||
dispatch(setDaemonStatus({ userId: uid, status: 'error' }));
|
||||
}
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('[useDaemonHealth] Failed to start daemon:', error);
|
||||
dispatch(setDaemonStatus({ userId: uid, status: 'error' }));
|
||||
return null;
|
||||
}
|
||||
}, [dispatch, userId]);
|
||||
}, [dispatch, uid, waitForAgentStatus]);
|
||||
|
||||
const stopDaemon = useCallback(async (): Promise<CommandResponse<ServiceStatus> | null> => {
|
||||
try {
|
||||
return await alphahumanServiceStop();
|
||||
dispatch(setDaemonStatus({ userId: uid, status: 'starting' }));
|
||||
const result = await alphahumanServiceStop();
|
||||
await waitForAgentStatus(false, 7000);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('[useDaemonHealth] Failed to stop daemon:', error);
|
||||
return null;
|
||||
}
|
||||
}, []);
|
||||
}, [dispatch, uid, waitForAgentStatus]);
|
||||
|
||||
const restartDaemon = useCallback(async (): Promise<boolean> => {
|
||||
const uid = userId || '__pending__';
|
||||
try {
|
||||
dispatch(setIsRecovering({ userId: uid, isRecovering: true }));
|
||||
dispatch(setDaemonStatus({ userId: uid, status: 'starting' }));
|
||||
|
||||
// Stop first
|
||||
const stopResult = await alphahumanServiceStop();
|
||||
if (!stopResult?.result || stopResult.result.state !== 'Stopped') {
|
||||
console.warn('[useDaemonHealth] Stop daemon failed, but continuing with start');
|
||||
}
|
||||
await alphahumanServiceStop();
|
||||
await waitForAgentStatus(false, 7000);
|
||||
|
||||
// Wait a moment for clean shutdown
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
// Start again
|
||||
const startResult = await alphahumanServiceStart();
|
||||
const success = startResult?.result && startResult.result.state === 'Running';
|
||||
await alphahumanServiceStart();
|
||||
const success = await waitForAgentStatus(true, 12000);
|
||||
|
||||
if (success) {
|
||||
dispatch(resetConnectionAttempts({ userId: uid }));
|
||||
} else {
|
||||
dispatch(setDaemonStatus({ userId: uid, status: 'error' }));
|
||||
}
|
||||
|
||||
dispatch(setIsRecovering({ userId: uid, isRecovering: false }));
|
||||
@@ -90,19 +131,24 @@ export const useDaemonHealth = (userId?: string) => {
|
||||
} catch (error) {
|
||||
console.error('[useDaemonHealth] Failed to restart daemon:', error);
|
||||
dispatch(setIsRecovering({ userId: uid, isRecovering: false }));
|
||||
dispatch(setDaemonStatus({ userId: uid, status: 'error' }));
|
||||
return false;
|
||||
}
|
||||
}, [dispatch, userId]);
|
||||
}, [dispatch, uid, waitForAgentStatus]);
|
||||
|
||||
const checkDaemonStatus =
|
||||
useCallback(async (): Promise<CommandResponse<ServiceStatus> | null> => {
|
||||
try {
|
||||
return await alphahumanServiceStatus();
|
||||
const running = await probeAgentStatus();
|
||||
if (running) {
|
||||
return await alphahumanServiceStatus();
|
||||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('[useDaemonHealth] Failed to check daemon status:', error);
|
||||
return null;
|
||||
}
|
||||
}, []);
|
||||
}, [probeAgentStatus]);
|
||||
|
||||
const setAutoStart = useCallback(
|
||||
(enabled: boolean) => {
|
||||
@@ -124,6 +170,10 @@ export const useDaemonHealth = (userId?: string) => {
|
||||
// Get uptime in human readable format
|
||||
const uptimeText = healthSnapshot ? formatUptime(healthSnapshot.uptime_seconds) : 'Unknown';
|
||||
|
||||
useEffect(() => {
|
||||
void probeAgentStatus();
|
||||
}, [probeAgentStatus]);
|
||||
|
||||
return {
|
||||
// State
|
||||
status,
|
||||
|
||||
+10
-57
@@ -1,14 +1,11 @@
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import ConnectionIndicator from '../components/ConnectionIndicator';
|
||||
import SkillsGrid from '../components/SkillsGrid';
|
||||
import { useUser } from '../hooks/useUser';
|
||||
import { TELEGRAM_BOT_USERNAME } from '../utils/config';
|
||||
import { openUrl } from '../utils/openUrl';
|
||||
|
||||
const Home = () => {
|
||||
const navigate = useNavigate();
|
||||
const { user } = useUser();
|
||||
const navigate = useNavigate();
|
||||
const userName = user?.firstName || 'User';
|
||||
|
||||
// Get greeting based on time
|
||||
@@ -19,18 +16,11 @@ const Home = () => {
|
||||
return 'Good evening';
|
||||
};
|
||||
|
||||
// Handle Telegram bot link
|
||||
// Open in-app conversations window
|
||||
const handleStartCooking = async () => {
|
||||
await openUrl(`https://t.me/${TELEGRAM_BOT_USERNAME}`);
|
||||
navigate('/conversations');
|
||||
};
|
||||
|
||||
const handleUpgrade = () => {
|
||||
navigate('/settings/billing');
|
||||
};
|
||||
|
||||
const currentPlan = user?.subscription?.plan || 'FREE';
|
||||
const showUpgradeCTA = currentPlan === 'FREE';
|
||||
|
||||
return (
|
||||
<div className="min-h-full relative">
|
||||
{/* Content overlay */}
|
||||
@@ -38,48 +28,6 @@ const Home = () => {
|
||||
{/* Main content */}
|
||||
<div className="flex-1 flex items-center justify-center p-4">
|
||||
<div className="max-w-md w-full">
|
||||
{/* Upgrade CTA */}
|
||||
{showUpgradeCTA && (
|
||||
<button
|
||||
onClick={handleUpgrade}
|
||||
className="glass rounded-3xl p-4 shadow-large animate-fade-up mb-4 w-full text-left hover:bg-stone-800/30 transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-primary-500/50 group">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<svg
|
||||
className="w-5 h-5 text-primary-500"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M13 10V3L4 14h7v7l9-11h-7z"
|
||||
/>
|
||||
</svg>
|
||||
<span className="font-semibold text-sm">Upgrade to Premium</span>
|
||||
</div>
|
||||
<p className="text-xs opacity-70">
|
||||
Unlock advanced features and unlimited access
|
||||
</p>
|
||||
</div>
|
||||
<svg
|
||||
className="w-5 h-5 opacity-60 group-hover:opacity-100 transition-opacity"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M9 5l7 7-7 7"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Weather card */}
|
||||
<div className="glass rounded-3xl p-4 shadow-large animate-fade-up text-center">
|
||||
{/* Greeting */}
|
||||
@@ -98,8 +46,13 @@ const Home = () => {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Skills Grid */}
|
||||
<SkillsGrid />
|
||||
<div className="mt-4 mb-8">
|
||||
<button
|
||||
onClick={() => navigate('/skills')}
|
||||
className="btn-secondary w-full py-2.5 text-sm font-medium rounded-xl">
|
||||
Open Skills Page
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import OAuthProviderButton from '../components/oauth/OAuthProviderButton';
|
||||
import { getProviderConfig } from '../components/oauth/providerConfigs';
|
||||
import RotatingTetrahedronCanvas from '../components/RotatingTetrahedronCanvas';
|
||||
|
||||
const googleProvider = getProviderConfig('google');
|
||||
|
||||
const Welcome = () => {
|
||||
return (
|
||||
<div className="min-h-full w-full bg-[#090b12] px-4 py-6 md:px-10 md:py-10">
|
||||
@@ -20,12 +22,8 @@ const Welcome = () => {
|
||||
Your AI superhuman for personal and business life.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap items-center justify-center gap-3">
|
||||
<Link
|
||||
className="inline-flex bg-[#201732] px-5 py-2 text-sm font-medium tracking-wide text-[#d4c8ff] transition-colors hover:bg-[#2a1d44]"
|
||||
to="/login">
|
||||
Get Started
|
||||
</Link>
|
||||
<div className="flex w-full max-w-sm flex-col items-stretch">
|
||||
{googleProvider ? <OAuthProviderButton provider={googleProvider} /> : null}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -63,9 +63,6 @@ const Onboarding = () => {
|
||||
return (
|
||||
<div className="min-h-full relative flex items-center justify-center">
|
||||
<div className="relative z-10 max-w-lg w-full mx-4">
|
||||
<div className="flex justify-center mb-6">
|
||||
<LottieAnimation src={stepAnimations[currentStep - 1]} height={120} width={120} />
|
||||
</div>
|
||||
<ProgressIndicator currentStep={currentStep} totalSteps={totalSteps} />
|
||||
{renderStep()}
|
||||
</div>
|
||||
|
||||
@@ -26,8 +26,8 @@ const FeaturesStep = ({ onNext }: FeaturesStepProps) => {
|
||||
return (
|
||||
<div className="glass rounded-3xl p-8 shadow-large animate-fade-up">
|
||||
<div className="text-center mb-4">
|
||||
<h1 className="text-xl font-bold mb-2">Are You Ready For This?</h1>
|
||||
<p className="opacity-70 text-sm">Here's some of the things that AlphaHuman can do</p>
|
||||
<h1 className="text-xl font-bold mb-2">Ready to get started?</h1>
|
||||
<p className="opacity-70 text-sm">Here's some of the things that OpenHuman can do</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 mb-4">
|
||||
|
||||
@@ -108,10 +108,7 @@ class ApiClient {
|
||||
|
||||
// Handle abort/timeout specifically
|
||||
if (error instanceof DOMException && error.name === 'AbortError') {
|
||||
throw {
|
||||
success: false,
|
||||
error: `Request timed out after ${timeout / 1000}s`,
|
||||
} as ApiError;
|
||||
throw { success: false, error: `Request timed out after ${timeout / 1000}s` } as ApiError;
|
||||
}
|
||||
|
||||
// Wrap network/other errors
|
||||
@@ -174,3 +171,5 @@ class ApiClient {
|
||||
|
||||
// Export singleton instance
|
||||
export const apiClient = new ApiClient(BACKEND_URL);
|
||||
|
||||
console.log('[ApiClient] Backend URL', BACKEND_URL);
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
export const BACKEND_URL = import.meta.env.VITE_BACKEND_URL || 'https://api.alphahuman.xyz';
|
||||
export const BACKEND_URL = import.meta.env.VITE_BACKEND_URL || 'https://api.tinyhumans.ai';
|
||||
|
||||
export const TELEGRAM_BOT_USERNAME =
|
||||
import.meta.env.VITE_TELEGRAM_BOT_USERNAME || 'alphahumanx_bot';
|
||||
|
||||
@@ -46,7 +46,7 @@ const handleAuthDeepLink = async (parsed: URL) => {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[DeepLink] Received auth token');
|
||||
console.log('[DeepLink] Received auth token', token);
|
||||
|
||||
try {
|
||||
await invoke('show_window');
|
||||
@@ -271,7 +271,7 @@ const handleDeepLinkUrls = async (urls: string[] | null | undefined) => {
|
||||
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
if (parsed.protocol !== 'alphahuman:') {
|
||||
if (parsed.protocol !== 'alphahuman:' && parsed.protocol !== 'openhuman:') {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -287,6 +287,11 @@ export interface ServiceStatus {
|
||||
details?: string | null;
|
||||
}
|
||||
|
||||
export interface AgentServerStatus {
|
||||
running: boolean;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface ConfigSnapshot {
|
||||
config: Record<string, unknown>;
|
||||
workspace_dir: string;
|
||||
@@ -564,6 +569,13 @@ export async function alphahumanServiceUninstall(): Promise<CommandResponse<Serv
|
||||
return await invoke('alphahuman_service_uninstall');
|
||||
}
|
||||
|
||||
export async function alphahumanAgentServerStatus(): Promise<CommandResponse<AgentServerStatus>> {
|
||||
if (!isTauri()) {
|
||||
throw new Error('Not running in Tauri');
|
||||
}
|
||||
return await invoke('alphahuman_agent_server_status');
|
||||
}
|
||||
|
||||
export async function runtimeListSkills(): Promise<SkillSnapshot[]> {
|
||||
if (!isTauri()) {
|
||||
throw new Error('Not running in Tauri');
|
||||
|
||||
Reference in New Issue
Block a user