169 lines
6.6 KiB
Rust
169 lines
6.6 KiB
Rust
//! IPC 命令:CLI 全链路(detectCli / previewAction / runAction / readConfig /
|
||
//! writeConfig / authStatus / authorize / diagnose,架构 §2 IPC 契约)
|
||
//!
|
||
//! 编排全部走 `agentdock-core::Engine`。runAction 通过 Tauri 事件流式回传
|
||
//! stdout/stderr;敏感输出经 `agentdock-secrets::redact` 脱敏后才进事件。
|
||
|
||
use std::collections::BTreeMap;
|
||
|
||
use agentdock_adapter::{AdapterAction, DryRunPlan};
|
||
use agentdock_core::{ActionEvent, ActionOpts, AuthFlowEvent, AuthStatus, ConfigFormState, ConfigVerifyResult, DetectResult, Engine, WriteResult};
|
||
use agentdock_diag::DiagnosticReport;
|
||
use serde_json::json;
|
||
use tauri::Emitter;
|
||
|
||
/// 把 action 字符串映射为 AdapterAction(snake_case,与 IPC 契约一致)。
|
||
fn parse_action(action: &str) -> Result<AdapterAction, String> {
|
||
match action {
|
||
"install" => Ok(AdapterAction::Install),
|
||
"update" => Ok(AdapterAction::Update),
|
||
"uninstall" => Ok(AdapterAction::Uninstall),
|
||
"write_config" => Ok(AdapterAction::WriteConfig),
|
||
"authorize" => Ok(AdapterAction::Authorize),
|
||
"repair" => Ok(AdapterAction::Repair),
|
||
other => Err(format!("未知动作: {other}")),
|
||
}
|
||
}
|
||
|
||
/// 读取单个适配器完整定义(CLI 详情页展示文档/渠道/许可用)。
|
||
#[tauri::command(rename = "getAdapter")]
|
||
pub fn get_adapter(id: String, state: tauri::State<'_, Engine>) -> Result<agentdock_adapter::Adapter, String> {
|
||
state.adapter(&id).map_err(|e| e.to_string())
|
||
}
|
||
|
||
/// 检测单个 CLI(detectCli)。
|
||
#[tauri::command(rename = "detectCli")]
|
||
pub fn detect_cli(id: String, state: tauri::State<'_, Engine>) -> Result<DetectResult, String> {
|
||
state.detect(&id).map_err(|e| e.to_string())
|
||
}
|
||
|
||
/// 批量检测全部 CLI(detectCliAll),供总览/目录/我的 CLI 接真机状态。
|
||
#[tauri::command(rename = "detectCliAll")]
|
||
pub fn detect_cli_all(state: tauri::State<'_, Engine>) -> Result<Vec<DetectResult>, String> {
|
||
state.detect_all().map_err(|e| e.to_string())
|
||
}
|
||
|
||
/// 干燥运行(previewAction):返回将执行的命令与影响面,不真正执行。
|
||
#[tauri::command(rename = "previewAction")]
|
||
pub fn preview_action(
|
||
id: String,
|
||
action: String,
|
||
channel: Option<String>,
|
||
state: tauri::State<'_, Engine>,
|
||
) -> Result<DryRunPlan, String> {
|
||
let action = parse_action(&action)?;
|
||
let mut plan = state.preview(&id, action).map_err(|e| e.to_string())?;
|
||
// 渠道覆盖时调整计划里的命令为所选渠道(供确认弹窗展示)
|
||
if let Some(ch) = channel {
|
||
if let Some(adapter) = state.adapter(&id).ok() {
|
||
if let Some(install) = adapter.install.as_ref() {
|
||
if let Some(target) = install.channels.iter().find(|c| c.id == ch) {
|
||
if !target.command.is_empty() {
|
||
plan.commands = vec![target.command.clone()];
|
||
plan.elevate = matches!(target.elevate.as_deref(), Some("if_needed") | Some("required"));
|
||
plan.elevate_reason_zh = target.elevate_reason_zh.clone();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
Ok(plan)
|
||
}
|
||
|
||
/// 流式执行动作(runAction):事件经 `cli-action-event` 回传。
|
||
#[tauri::command(rename = "runAction")]
|
||
pub fn run_action(
|
||
app: tauri::AppHandle,
|
||
id: String,
|
||
action: String,
|
||
channel: Option<String>,
|
||
state: tauri::State<'_, Engine>,
|
||
) -> Result<(), String> {
|
||
let action = parse_action(&action)?;
|
||
let engine = state.inner().clone();
|
||
let opts = ActionOpts { channel };
|
||
std::thread::spawn(move || {
|
||
let cli_id = id.clone();
|
||
let result = engine.run(&id, action, &opts, |ev| {
|
||
let _ = app.emit("cli-action-event", json!({ "cli_id": cli_id, "event": ev }));
|
||
});
|
||
if let Err(e) = result {
|
||
let _ = app.emit(
|
||
"cli-action-event",
|
||
json!({ "cli_id": cli_id, "event": ActionEvent::error(e.to_string()) }),
|
||
);
|
||
}
|
||
});
|
||
Ok(())
|
||
}
|
||
|
||
/// 读取配置表单状态(readConfig)。
|
||
#[tauri::command(rename = "readConfig")]
|
||
pub fn read_config(id: String, state: tauri::State<'_, Engine>) -> Result<ConfigFormState, String> {
|
||
state.read_config(&id).map_err(|e| e.to_string())
|
||
}
|
||
|
||
/// 写配置(writeConfig):patch 为 { field_id: value }。
|
||
#[tauri::command(rename = "writeConfig")]
|
||
pub fn write_config(
|
||
id: String,
|
||
patch: BTreeMap<String, String>,
|
||
state: tauri::State<'_, Engine>,
|
||
) -> Result<WriteResult, String> {
|
||
state.write_config(&id, &patch).map_err(|e| e.to_string())
|
||
}
|
||
|
||
/// 配置写入后的「生效检查」(verifyConfig):重读配置文件 + CLI 版本探测。
|
||
#[tauri::command(rename = "verifyConfig")]
|
||
pub fn verify_config(id: String, state: tauri::State<'_, Engine>) -> Result<ConfigVerifyResult, String> {
|
||
state.verify_config(&id).map_err(|e| e.to_string())
|
||
}
|
||
|
||
/// 授权状态(authStatus)。
|
||
#[tauri::command(rename = "authStatus")]
|
||
pub fn auth_status(id: String, state: tauri::State<'_, Engine>) -> Result<AuthStatus, String> {
|
||
state.auth_status(&id).map_err(|e| e.to_string())
|
||
}
|
||
|
||
/// 软件内授权(authorize):按官方授权方式运行登录命令,事件经 `cli-auth-event` 流式回传。
|
||
#[tauri::command(rename = "authorize")]
|
||
pub fn authorize(
|
||
app: tauri::AppHandle,
|
||
id: String,
|
||
mode: String,
|
||
state: tauri::State<'_, Engine>,
|
||
) -> Result<(), String> {
|
||
let engine = state.inner().clone();
|
||
std::thread::spawn(move || {
|
||
let cli_id = id.clone();
|
||
let m = mode.clone();
|
||
let result = engine.authorize_stream(&id, &mode, |ev| {
|
||
let _ = app.emit("cli-auth-event", &ev);
|
||
});
|
||
if let Err(e) = result {
|
||
let _ = app.emit("cli-auth-event", AuthFlowEvent::error(&cli_id, &m, e.to_string()));
|
||
}
|
||
});
|
||
Ok(())
|
||
}
|
||
|
||
/// 取消正在进行的授权流程。
|
||
#[tauri::command(rename = "cancelAuthorize")]
|
||
pub fn cancel_authorize(id: String, mode: String, state: tauri::State<'_, Engine>) {
|
||
state.cancel_authorize(&id, &mode);
|
||
}
|
||
|
||
/// 诊断(diagnose):内部实时取本机环境快照。
|
||
#[tauri::command(rename = "diagnose")]
|
||
pub fn diagnose(id: String, state: tauri::State<'_, Engine>) -> Result<DiagnosticReport, String> {
|
||
let env = agentdock_platform::detect::detect_env();
|
||
state.diagnose(&id, &env).map_err(|e| e.to_string())
|
||
}
|
||
|
||
/// 批量诊断全部已装工具(总览「立即诊断」入口用),内部实时取本机环境快照。
|
||
#[tauri::command(rename = "diagnoseAll")]
|
||
pub fn diagnose_all(state: tauri::State<'_, Engine>) -> Result<Vec<DiagnosticReport>, String> {
|
||
let env = agentdock_platform::detect::detect_env();
|
||
state.diagnose_all(&env).map_err(|e| e.to_string())
|
||
}
|