mod watcher;
use std::sync::{Arc, Mutex};
use serde_json::{json, Value};
use tauri::{AppHandle, Emitter, State};
use tasklens_core::agent::{self, AgentConfig, AgentSession};
use tasklens_core::dispatch;
use tasklens_core::settings::{self, Settings};
#[tauri::command]
fn vault_command(command: String, args: Option<Value>) -> Result<Value, String> {
dispatch::run(&command, &args.unwrap_or(Value::Null), None).map_err(|error| error.0)
}
#[tauri::command]
fn watch_vault(
app: AppHandle,
state: State<'_, Arc<watcher::VaultWatcher>>,
path: String,
) -> Result<(), String> {
state.watch(app, &path)
}
#[derive(Default)]
struct AgentState {
session: Mutex<Option<AgentSession>>,
}
#[tauri::command]
fn agent_start(
app: AppHandle,
state: State<'_, Arc<AgentState>>,
config: Value,
) -> Result<Value, String> {
let mut slot = state.session.lock().map_err(|_| "agent 状态不可用".to_string())?;
if let Some(existing) = slot.take() {
existing.stop();
}
let settings = Settings::load();
let config = AgentConfig::resolve(&config, &settings, settings::resolve_root(None).ok().as_deref());
let emitter = app.clone();
let emit: Arc<dyn Fn(String) + Send + Sync> = Arc::new(move |line: String| {
let data: Value = serde_json::from_str(&line).unwrap_or(Value::String(line));
let _ = emitter.emit("agent-event", data);
});
let session = AgentSession::start(&config, emit).map_err(|error| error.0)?;
let payload = json!({
"command": session.command,
"embedded": session.embedded,
"vault": config.vault,
"missingExtensions": config.missing_extensions(),
});
*slot = Some(session);
Ok(payload)
}
#[tauri::command]
fn agent_send(state: State<'_, Arc<AgentState>>, line: String) -> Result<(), String> {
let slot = state.session.lock().map_err(|_| "agent 状态不可用".to_string())?;
match slot.as_ref() {
Some(session) => session.send_line(&line).map_err(|error| error.0),
None => Err("agent 尚未启动".to_string()),
}
}
#[tauri::command]
fn agent_stop(state: State<'_, Arc<AgentState>>) -> Result<(), String> {
let mut slot = state.session.lock().map_err(|_| "agent 状态不可用".to_string())?;
if let Some(session) = slot.take() {
session.stop();
}
Ok(())
}
#[tauri::command]
fn agent_probe(command: String) -> Value {
let settings = Settings::load();
let requested = command.trim();
let name = if requested.is_empty() {
settings.ai.runtime.clone()
} else {
requested.to_string()
};
let name = if name == "auto" { "bun" } else { name.as_str() }.to_string();
let (path, needs_shell) = agent::resolve_command(&name);
let exists = path.is_file();
if !exists && name == "bun" {
let (node_path, node_shell) = agent::resolve_command("node");
if node_path.is_file() {
return json!({
"command": "node",
"path": node_path.to_string_lossy(),
"exists": true,
"needsShell": node_shell,
});
}
let _ = needs_shell;
}
json!({
"command": name,
"path": path.to_string_lossy(),
"exists": exists,
"needsShell": needs_shell,
})
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_opener::init())
.manage(Arc::new(watcher::VaultWatcher::new()))
.manage(Arc::new(AgentState::default()))
.invoke_handler(tauri::generate_handler![
vault_command,
watch_vault,
agent_start,
agent_send,
agent_stop,
agent_probe
])
.run(tauri::generate_context!())
.expect("TaskLens 启动失败");
}