Wave 2.2: 流式安装输出、软件内授权四模式、本机环境一键装与总览缓存

EOF

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
leefer
2026-08-25 17:11:14 +08:00
co-authored by Cursor
parent a2fd5e369b
commit dd5a9378d2
24 changed files with 2659 additions and 263 deletions
+36 -1
View File
@@ -7,7 +7,7 @@
use std::collections::BTreeMap;
use agentdock_adapter::{AdapterAction, DryRunPlan};
use agentdock_core::{ActionEvent, ActionOpts, AuthStatus, ConfigFormState, ConfigVerifyResult, DetectResult, Engine, WriteResult};
use agentdock_core::{ActionEvent, ActionOpts, AuthFlowEvent, AuthStatus, ConfigFormState, ConfigVerifyResult, DetectResult, Engine, WriteResult};
use agentdock_diag::DiagnosticReport;
use serde_json::json;
use tauri::Emitter;
@@ -125,9 +125,44 @@ pub fn auth_status(id: String, state: tauri::State<'_, Engine>) -> Result<AuthSt
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())
}
@@ -1,3 +1,4 @@
pub mod catalog;
pub mod cli;
pub mod env;
pub mod runtime;
@@ -0,0 +1,85 @@
//! IPC 命令:本机环境运行时一键安装(Wave 2.2 Req 3
//!
//! 下载仅限官方白名单(`RuntimeSource.allowed_hosts`),下载前经
//! `is_url_host_allowed` 校验;下载用系统 `curl.exe`;下载成功后用
//! `open_with_shell` 打开安装向导(用户在向导里点完即装)。失败兜底
//! 提供「打开官方下载页」。应用本身不提权、不静默安装。
use agentdock_core::{
is_url_host_allowed, open_with_shell, source_for, RuntimeSource,
};
use serde_json::json;
use tauri::Emitter;
/// 预览运行时安装来源(确认弹窗展示官方地址/体积/权限)。
#[tauri::command(rename = "previewRuntimeInstall")]
pub fn preview_runtime_install(runtime: String) -> Result<RuntimeSource, String> {
source_for(&runtime).ok_or_else(|| format!("未知运行时: {runtime}"))
}
/// 兜底:打开运行时官方下载页(默认浏览器)。
#[tauri::command(rename = "openRuntimePage")]
pub fn open_runtime_page(runtime: String) -> Result<(), String> {
let src = source_for(&runtime).ok_or_else(|| format!("未知运行时: {runtime}"))?;
open_with_shell(&src.download_page).map_err(|e| e.to_string())
}
/// 一键安装运行时:下载官方安装包 → 打开安装向导。进度经 `runtime-install-event` 回传。
#[tauri::command(rename = "installRuntime")]
pub fn install_runtime(app: tauri::AppHandle, runtime: String) -> Result<(), String> {
let src = source_for(&runtime).ok_or_else(|| format!("未知运行时: {runtime}"))?;
std::thread::spawn(move || {
let emit = |app: &tauri::AppHandle, payload: serde_json::Value| {
let _ = app.emit("runtime-install-event", payload);
};
// 不可直接下载的运行时(如 uv):直接打开官方下载页
let Some(url) = src.download_url.clone().filter(|_| src.direct_installable) else {
let _ = open_with_shell(&src.download_page);
emit(
&app,
json!({ "runtime": runtime, "phase": "opened_page", "message": format!("已打开{}官方下载页", src.label_zh) }),
);
return;
};
// 安全红线:来源必须落白名单
emit(&app, json!({ "runtime": runtime, "phase": "validate", "message": format!("正在校验来源({})…", src.source_label) }));
if !is_url_host_allowed(&url, &src.allowed_hosts) {
emit(
&app,
json!({ "runtime": runtime, "phase": "error", "message": "下载来源不在官方白名单内,已中止(安全红线)", "fallback": true }),
);
return;
}
// 下载到临时目录
let dir = std::env::temp_dir().join("agentdock-downloads");
let _ = std::fs::create_dir_all(&dir);
let file_name = url.rsplit('/').next().unwrap_or("installer.bin");
let dest = dir.join(file_name);
emit(&app, json!({ "runtime": runtime, "phase": "download", "message": format!("正在下载 {}{},来自 {})…", src.label_zh, src.size_approx, src.source_label) }));
match agentdock_core::download_with_curl(&url, &dest) {
Ok(()) => {
emit(&app, json!({ "runtime": runtime, "phase": "open", "message": "下载完成,正在打开安装向导(请在向导中完成安装)…" }));
if let Err(e) = open_with_shell(&dest.to_string_lossy()) {
emit(
&app,
json!({ "runtime": runtime, "phase": "error", "message": format!("无法打开安装向导:{e}"), "fallback": true }),
);
return;
}
emit(&app, json!({ "runtime": runtime, "phase": "done", "message": "安装向导已打开" }));
}
Err(e) => {
emit(
&app,
json!({ "runtime": runtime, "phase": "error", "message": format!("下载失败:{e}。可点击「打开官方下载页」手动下载。"), "fallback": true }),
);
}
}
});
Ok(())
}
+6
View File
@@ -56,7 +56,13 @@ pub fn run() {
commands::cli::write_config,
commands::cli::verify_config,
commands::cli::auth_status,
commands::cli::authorize,
commands::cli::cancel_authorize,
commands::cli::diagnose,
commands::cli::diagnose_all,
commands::runtime::preview_runtime_install,
commands::runtime::open_runtime_page,
commands::runtime::install_runtime,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");