diff --git a/install.sh b/install.sh index 4d2b42b..4d112b0 100755 --- a/install.sh +++ b/install.sh @@ -292,6 +292,10 @@ case $MODE in sh "$SRC/deploy/linux/install.sh" >/dev/null || die "安裝失敗。" "執行 sh $SRC/deploy/linux/install.sh 看錯誤。" ok "程式放在 ~/.local/bin/anybot" [ "$MODE" = app ] && ok "應用程式選單裡有 AnyBot" + # 更新: the one running in the background (anybot setup's service) switches to the new one. + if systemctl --user is-active --quiet anybot 2>/dev/null; then + systemctl --user restart anybot && ok "重新啟動背景執行的 AnyBot(換成新版)" + fi fi fi PATH="$HOME/.local/bin:$PATH" diff --git a/src/agent.rs b/src/agent.rs index 59231fe..7b799dc 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -630,6 +630,9 @@ pub fn spawn(job: Job, bot_id: u64, tx: Sender<(u64, StreamEvent)>, cancel: Arc< // Permission to use computers at all, and this Mac in particular, is asked for // when it's needed instead of the step failing quietly. let mut gate: Result<(), String> = Ok(()); + // The person just said yes to this very step in a switch's pop-up (it shows the + // step): no second pop-up for it. + let mut said_yes = false; // 直接操作: like Grok Bot after its one-time setup ("讓 Grok Bot 在你的電腦上開啟檔案和 // 執行任務。自動審核仍會先檢查所有操作", ENHi8c), nothing is asked except what the // safety checks and the person's "一律詢問" rules stop. @@ -642,8 +645,11 @@ pub fn spawn(job: Job, bot_id: u64, tx: Sender<(u64, StreamEvent)>, cancel: Arc< if !tools_on { let reason = format!("讓 {} 使用電腦工具:{}\n(在 {} 上)", job.via_name, what, t.name); match ask_person(ApprovalKind::EnableTools, reason, "tools:on".into()) { - ApprovalAnswer::Once => {} - ApprovalAnswer::Always(_) => tools_on = true, + ApprovalAnswer::Once => said_yes = true, + ApprovalAnswer::Always(_) => { + tools_on = true; + said_yes = true; + } ApprovalAnswer::Reject => gate = Err("使用者沒有允許使用電腦工具。請改用文字回答,或問使用者想怎麼做。".into()), } } @@ -654,8 +660,11 @@ pub fn spawn(job: Job, bot_id: u64, tx: Sender<(u64, StreamEvent)>, cancel: Arc< if gate.is_ok() && matches!(t.place, tools::Place::Local { .. }) && !local_on { let reason = format!("在你的電腦上執行任務,而不只是在它自己的電腦上:{}{what}\n(在 {} 上)", if is_web { format!("{} ", web::label(&call.name)) } else { String::new() }, t.name); match ask_person(ApprovalKind::EnableThisComputer, reason, "local:on".into()) { - ApprovalAnswer::Once => {} - ApprovalAnswer::Always(_) => local_on = true, + ApprovalAnswer::Once => said_yes = true, + ApprovalAnswer::Always(_) => { + local_on = true; + said_yes = true; + } ApprovalAnswer::Reject => gate = Err(format!("使用者沒有允許在「{}」上執行。可以改用「{}」,或問使用者。", t.name, crate::model::BOT_COMPUTER_NAME)), } } @@ -764,6 +773,8 @@ pub fn spawn(job: Job, bot_id: u64, tx: Sender<(u64, StreamEvent)>, cancel: Arc< let approved = match decision { _ if gate.is_err() => Err((gate.clone().unwrap_err(), ToolStatus::Denied)), Decision::Allow => Ok(()), + // Sending (Messages, mail…) still asks every time, even right after a switch. + Decision::Ask { .. } if said_yes && !(is_mac && crate::macapps::is_send(&call.name)) => Ok(()), Decision::Deny(r) => Err((format!("被安全規則擋下:{r}"), ToolStatus::Denied)), Decision::Ask { reason, grant } => { let machine = target.as_ref().map(|t| t.name.clone()).unwrap_or_default(); diff --git a/src/app.rs b/src/app.rs index 0770bfb..4a6172e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -698,6 +698,8 @@ impl App { if crate::computer::screen_supported() { screens.push(crate::model::THIS_COMPUTER_NAME.to_string()); } + // …and the account's other computers (手腳): their own AnyBot looks and clicks there. + screens.extend(devices.iter().cloned()); if cfg!(target_os = "macos") { tool_specs.extend(crate::macapps::specs().into_iter().filter(|t| self.state.messages_send || t["name"] != crate::macapps::SEND_MESSAGE)); } diff --git a/src/remote.rs b/src/remote.rs index f032fd3..6c5517e 100644 --- a/src/remote.rs +++ b/src/remote.rs @@ -720,6 +720,10 @@ pub fn why_unreachable(e: &ureq::Error) -> String { use std::io::ErrorKind as K; use ureq::Error as E; let text = e.to_string().to_lowercase(); + // A TLS failure often comes wrapped as an I/O error: look at the words first. + if text.contains("certificate") || text.contains("tls") || text.contains("handshake") { + return "和伺服器的安全連線失敗:對方的憑證不對(網址可能連到了別的服務,或憑證過期)".to_string(); + } let w = match e { E::HostNotFound => "找不到伺服器:這台電腦可能沒連上網路,或伺服器網址打錯了", E::ConnectionFailed => "伺服器沒有回應:可能沒開機,或網路把連線擋住了", @@ -731,7 +735,6 @@ pub fn why_unreachable(e: &ureq::Error) -> String { K::NetworkUnreachable | K::HostUnreachable | K::NetworkDown | K::AddrNotAvailable => "這台電腦沒有連上網路", _ => "網路連線出了問題", }, - _ if text.contains("tls") || text.contains("certificate") => "和伺服器的安全連線失敗(伺服器的憑證有問題)", _ if text.contains("dns") || text.contains("resolve") => "找不到伺服器:這台電腦可能沒連上網路,或伺服器網址打錯了", _ => "網路連線出了問題", };