From 0d69f2b4756feedfc2f3773a28eef9b853c3ed25 Mon Sep 17 00:00:00 2001 From: zhom <2717306+zhom@users.noreply.github.com> Date: Fri, 6 Nov 2026 22:45:25 +0400 Subject: [PATCH] feat: cloud update notice --- src-tauri/src/mcp_server.rs | 77 +++++++++++++++++++--------- src/components/cookie-bot-shared.tsx | 1 + src/i18n/locales/de.json | 2 + src/i18n/locales/en.json | 2 + src/i18n/locales/es.json | 2 + src/i18n/locales/fr.json | 2 + src/i18n/locales/ja.json | 2 + src/i18n/locales/ko.json | 2 + src/i18n/locales/pt.json | 2 + src/i18n/locales/ru.json | 2 + src/i18n/locales/tr.json | 2 + src/i18n/locales/vi.json | 2 + src/i18n/locales/zh.json | 2 + src/lib/backend-errors.ts | 3 ++ src/lib/cookie-bot-outcomes.test.mjs | 1 + 15 files changed, 80 insertions(+), 24 deletions(-) diff --git a/src-tauri/src/mcp_server.rs b/src-tauri/src/mcp_server.rs index 393d02d6..e9e6b33a 100644 --- a/src-tauri/src/mcp_server.rs +++ b/src-tauri/src/mcp_server.rs @@ -13228,10 +13228,30 @@ impl McpServer { /// to do next; a stable code and its parameters are something it can branch /// on, and it is the same envelope the desktop and the REST API answer with. fn cloud_error(err: crate::cookie_bot::CookieBotError) -> McpError { - McpError { - code: -32000, - message: err.to_error_json(), - data: None, + Self::cloud_failure(err.to_error_json()) + } + + /// A cloud failure for an agent. A code with a sentence of its own gets that + /// sentence as the message and the envelope as data; any other code keeps + /// the envelope as the message. + fn cloud_failure(envelope: String) -> McpError { + let parsed = serde_json::from_str::(&envelope).ok(); + let sentence = parsed + .as_ref() + .and_then(|value| value.get("code")) + .and_then(|code| code.as_str()) + .and_then(cloud_failure_sentence); + match sentence { + Some(text) => McpError { + code: -32000, + message: text.to_string(), + data: parsed, + }, + None => McpError { + code: -32000, + message: envelope, + data: None, + }, } } @@ -13310,11 +13330,7 @@ impl McpServer { let outcome = crate::remote_session::start_remote_session(app, &profile, url) .await - .map_err(|e| McpError { - code: -32000, - message: e.to_error_json(), - data: None, - })?; + .map_err(|e| Self::cloud_failure(e.to_error_json()))?; Self::json_content(&outcome) } @@ -13325,22 +13341,14 @@ impl McpServer { let session_id = Self::require_str(arguments, "session_id")?; let outcome = crate::remote_session::end_remote_session(session_id) .await - .map_err(|e| McpError { - code: -32000, - message: e.to_error_json(), - data: None, - })?; + .map_err(|e| Self::cloud_failure(e.to_error_json()))?; Self::json_content(&outcome) } async fn handle_list_remote_sessions() -> Result { let sessions = crate::remote_session::list_remote_sessions() .await - .map_err(|e| McpError { - code: -32000, - message: e.to_error_json(), - data: None, - })?; + .map_err(|e| Self::cloud_failure(e.to_error_json()))?; Self::json_content(&sessions) } @@ -13350,11 +13358,7 @@ impl McpServer { let session_id = Self::require_str(arguments, "session_id")?; let state = crate::remote_session::get_remote_session(session_id) .await - .map_err(|e| McpError { - code: -32000, - message: e.to_error_json(), - data: None, - })?; + .map_err(|e| Self::cloud_failure(e.to_error_json()))?; Self::json_content(&state) } @@ -13562,6 +13566,16 @@ impl McpServer { static MCP_SERVER: std::sync::LazyLock = std::sync::LazyLock::new(McpServer::new); +/// What an agent reads for a cloud code it should relay to the person. +fn cloud_failure_sentence(code: &str) -> Option<&'static str> { + match code { + "CLOUD_BROWSER_UPDATING" => Some( + "The cloud browser is updating to this profile's Wayfern version. Try again in a few minutes.", + ), + _ => None, + } +} + #[cfg(test)] #[path = "mcp_server_browser_tests.rs"] mod browser_tests; @@ -13570,6 +13584,21 @@ mod browser_tests; mod tests { use super::*; + #[test] + fn a_cloud_update_refusal_reads_as_a_sentence_with_its_envelope() { + let envelope = r#"{"code":"CLOUD_BROWSER_UPDATING","params":{"cloudVersion":"1.0.0.1","profileVersion":"1.0.0.2"}}"#; + let error = McpServer::cloud_failure(envelope.to_string()); + assert!(error.message.starts_with("The cloud browser is updating")); + let data = error.data.expect("envelope as data"); + assert_eq!(data["code"], "CLOUD_BROWSER_UPDATING"); + assert_eq!(data["params"]["profileVersion"], "1.0.0.2"); + + let other = r#"{"code":"REMOTE_NO_CAPACITY"}"#; + let error = McpServer::cloud_failure(other.to_string()); + assert_eq!(error.message, other); + assert!(error.data.is_none()); + } + #[test] fn an_engine_plan_refusal_is_explained_by_a_device_refusal() { let engine = || { diff --git a/src/components/cookie-bot-shared.tsx b/src/components/cookie-bot-shared.tsx index a8d66ca2..e1598145 100644 --- a/src/components/cookie-bot-shared.tsx +++ b/src/components/cookie-bot-shared.tsx @@ -659,6 +659,7 @@ export const OUTCOME_KEYS: Record = { quota_exhausted: "cookieBot.outcome.quotaExhausted", profile_locked: "cookieBot.outcome.profileLocked", no_capacity: "cookieBot.outcome.noCapacity", + cloud_browser_updating: "cookieBot.outcome.cloudBrowserUpdating", manager_error: "cookieBot.outcome.managerError", budget_exceeded: "cookieBot.outcome.budgetExceeded", // The commonest real outcome of the five missing ones, and it was showing diff --git a/src/i18n/locales/de.json b/src/i18n/locales/de.json index 1408662f..02ad5b0a 100644 --- a/src/i18n/locales/de.json +++ b/src/i18n/locales/de.json @@ -2689,6 +2689,7 @@ "cloudNotSignedIn": "Melden Sie sich bei Ihrem Donut Browser-Konto an, um dies zu nutzen.", "cloudUnreachable": "Die Server von Donut Browser sind nicht erreichbar. Prüfen Sie Ihre Verbindung und versuchen Sie es erneut.", "cloudRequestFailed": "Die Anfrage ist fehlgeschlagen. Bitte versuchen Sie es gleich noch einmal.", + "cloudBrowserUpdating": "Der Cloud-Browser wird auf Ihre Wayfern-Version aktualisiert. Versuchen Sie es in ein paar Minuten erneut.", "remoteRateLimited": "Zu viele Anfragen. Warten Sie einen Moment und versuchen Sie es erneut.", "remoteNoCapacity": "Derzeit ist kein entfernter Host frei. Versuchen Sie es in einigen Minuten erneut.", "remoteNotEntitled": "Ihr Tarif umfasst keine entfernte Ausführung.", @@ -3721,6 +3722,7 @@ "quotaExhausted": "Remote-Stunden aufgebraucht", "profileLocked": "Das Profil war an anderer Stelle geöffnet", "noCapacity": "Kein Rechner war frei", + "cloudBrowserUpdating": "Der Cloud-Browser wurde gerade auf Ihre Wayfern-Version aktualisiert", "managerError": "Die Remote-Rechner konnten den Browser nicht starten", "budgetExceeded": "Das Zeitbudget des Durchlaufs war aufgebraucht", "browsingIncomplete": "Der Browser wurde geöffnet, aber das Surfen wurde nicht abgeschlossen", diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 523c7bef..8b152ef3 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -2689,6 +2689,7 @@ "cloudNotSignedIn": "Sign in to your Donut Browser account to use this.", "cloudUnreachable": "Could not reach Donut Browser's servers. Check your connection and try again.", "cloudRequestFailed": "The request failed. Please try again in a moment.", + "cloudBrowserUpdating": "The cloud browser is updating to your Wayfern version. Try again in a few minutes.", "remoteRateLimited": "Too many requests. Wait a moment and try again.", "remoteNoCapacity": "No remote host is free right now. Try again in a few minutes.", "remoteNotEntitled": "Your plan does not include remote execution.", @@ -3721,6 +3722,7 @@ "quotaExhausted": "Remote hours used up", "profileLocked": "The profile was open somewhere else", "noCapacity": "No machine was free", + "cloudBrowserUpdating": "The cloud browser was updating to your Wayfern version", "managerError": "The fleet could not start the browser", "budgetExceeded": "The run's time budget ran out", "browsingIncomplete": "The browser opened but the browsing did not finish", diff --git a/src/i18n/locales/es.json b/src/i18n/locales/es.json index 4a92db8f..34b2b3b5 100644 --- a/src/i18n/locales/es.json +++ b/src/i18n/locales/es.json @@ -2708,6 +2708,7 @@ "cloudNotSignedIn": "Inicia sesión en tu cuenta de Donut Browser para usar esto.", "cloudUnreachable": "No se pudo contactar con los servidores de Donut Browser. Comprueba tu conexión e inténtalo de nuevo.", "cloudRequestFailed": "La solicitud falló. Inténtalo de nuevo en un momento.", + "cloudBrowserUpdating": "El navegador en la nube se está actualizando a tu versión de Wayfern. Vuelve a intentarlo en unos minutos.", "remoteRateLimited": "Demasiadas solicitudes. Espera un momento e inténtalo de nuevo.", "remoteNoCapacity": "Ahora mismo no hay ninguna máquina remota libre. Inténtalo de nuevo en unos minutos.", "remoteNotEntitled": "Tu plan no incluye la ejecución remota.", @@ -3759,6 +3760,7 @@ "quotaExhausted": "Horas remotas agotadas", "profileLocked": "El perfil estaba abierto en otro lugar", "noCapacity": "No había ninguna máquina libre", + "cloudBrowserUpdating": "El navegador en la nube se estaba actualizando a tu versión de Wayfern", "managerError": "La flota no pudo iniciar el navegador", "budgetExceeded": "Se agotó el tiempo previsto para la ejecución", "browsingIncomplete": "El navegador se abrió, pero la navegación no terminó", diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json index 4cb59f49..9babf4b2 100644 --- a/src/i18n/locales/fr.json +++ b/src/i18n/locales/fr.json @@ -2708,6 +2708,7 @@ "cloudNotSignedIn": "Connectez-vous à votre compte Donut Browser pour utiliser cette fonction.", "cloudUnreachable": "Impossible de joindre les serveurs de Donut Browser. Vérifiez votre connexion et réessayez.", "cloudRequestFailed": "La requête a échoué. Réessayez dans un instant.", + "cloudBrowserUpdating": "Le navigateur cloud se met à jour vers votre version de Wayfern. Réessayez dans quelques minutes.", "remoteRateLimited": "Trop de requêtes. Patientez un instant et réessayez.", "remoteNoCapacity": "Aucune machine distante n'est libre pour le moment. Réessayez dans quelques minutes.", "remoteNotEntitled": "Votre forfait n'inclut pas l'exécution à distance.", @@ -3759,6 +3760,7 @@ "quotaExhausted": "Heures distantes épuisées", "profileLocked": "Le profil était ouvert ailleurs", "noCapacity": "Aucune machine n'était libre", + "cloudBrowserUpdating": "Le navigateur cloud se mettait à jour vers votre version de Wayfern", "managerError": "La flotte n'a pas pu démarrer le navigateur", "budgetExceeded": "Le temps prévu pour l'exécution est épuisé", "browsingIncomplete": "Le navigateur s'est ouvert mais la navigation n'est pas allée à son terme", diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json index 3f53d6a0..c76377fc 100644 --- a/src/i18n/locales/ja.json +++ b/src/i18n/locales/ja.json @@ -2689,6 +2689,7 @@ "cloudNotSignedIn": "この機能を使うには Donut Browser アカウントにサインインしてください。", "cloudUnreachable": "Donut Browser のサーバーに接続できませんでした。接続を確認してもう一度お試しください。", "cloudRequestFailed": "リクエストに失敗しました。しばらくしてからもう一度お試しください。", + "cloudBrowserUpdating": "クラウドブラウザーをお使いの Wayfern のバージョンに更新しています。数分後にもう一度お試しください。", "remoteRateLimited": "リクエストが多すぎます。少し待ってからもう一度お試しください。", "remoteNoCapacity": "現在空いているリモートマシンがありません。数分後にもう一度お試しください。", "remoteNotEntitled": "ご利用のプランにはリモート実行が含まれていません。", @@ -3721,6 +3722,7 @@ "quotaExhausted": "リモート時間を使い切りました", "profileLocked": "プロファイルが別の場所で開かれていました", "noCapacity": "空きマシンがありませんでした", + "cloudBrowserUpdating": "クラウドブラウザーをお使いの Wayfern のバージョンに更新中でした", "managerError": "フリートがブラウザーを起動できませんでした", "budgetExceeded": "実行の持ち時間を使い切りました", "browsingIncomplete": "ブラウザーは起動しましたが、巡回が完了しませんでした", diff --git a/src/i18n/locales/ko.json b/src/i18n/locales/ko.json index e63ec972..d3439602 100644 --- a/src/i18n/locales/ko.json +++ b/src/i18n/locales/ko.json @@ -2689,6 +2689,7 @@ "cloudNotSignedIn": "이 기능을 사용하려면 Donut Browser 계정에 로그인하세요.", "cloudUnreachable": "Donut Browser 서버에 연결하지 못했습니다. 연결 상태를 확인하고 다시 시도하세요.", "cloudRequestFailed": "요청이 실패했습니다. 잠시 후 다시 시도하세요.", + "cloudBrowserUpdating": "클라우드 브라우저를 사용 중인 Wayfern 버전으로 업데이트하고 있습니다. 몇 분 후에 다시 시도하세요.", "remoteRateLimited": "요청이 너무 많습니다. 잠시 기다렸다가 다시 시도하세요.", "remoteNoCapacity": "지금은 사용 가능한 원격 머신이 없습니다. 몇 분 후에 다시 시도하세요.", "remoteNotEntitled": "현재 요금제에는 원격 실행이 포함되어 있지 않습니다.", @@ -3721,6 +3722,7 @@ "quotaExhausted": "원격 시간을 모두 사용했습니다", "profileLocked": "프로필이 다른 곳에서 열려 있었습니다", "noCapacity": "여유 머신이 없었습니다", + "cloudBrowserUpdating": "클라우드 브라우저를 사용 중인 Wayfern 버전으로 업데이트하는 중이었습니다", "managerError": "플릿이 브라우저를 시작하지 못했습니다", "budgetExceeded": "실행 시간 예산이 소진되었습니다", "browsingIncomplete": "브라우저는 열렸지만 탐색이 끝나지 않았습니다", diff --git a/src/i18n/locales/pt.json b/src/i18n/locales/pt.json index 2a536581..cf322d4c 100644 --- a/src/i18n/locales/pt.json +++ b/src/i18n/locales/pt.json @@ -2708,6 +2708,7 @@ "cloudNotSignedIn": "Entre na sua conta do Donut Browser para usar este recurso.", "cloudUnreachable": "Não foi possível contatar os servidores do Donut Browser. Verifique sua conexão e tente novamente.", "cloudRequestFailed": "A solicitação falhou. Tente novamente em instantes.", + "cloudBrowserUpdating": "O navegador na nuvem está sendo atualizado para a sua versão do Wayfern. Tente novamente em alguns minutos.", "remoteRateLimited": "Solicitações demais. Aguarde um momento e tente novamente.", "remoteNoCapacity": "Nenhuma máquina remota está livre agora. Tente novamente em alguns minutos.", "remoteNotEntitled": "Seu plano não inclui execução remota.", @@ -3759,6 +3760,7 @@ "quotaExhausted": "Horas remotas esgotadas", "profileLocked": "O perfil estava aberto noutro lugar", "noCapacity": "Nenhuma máquina estava livre", + "cloudBrowserUpdating": "O navegador na nuvem estava sendo atualizado para a sua versão do Wayfern", "managerError": "A frota não conseguiu iniciar o navegador", "budgetExceeded": "O tempo previsto para a execução acabou", "browsingIncomplete": "O navegador abriu, mas a navegação não terminou", diff --git a/src/i18n/locales/ru.json b/src/i18n/locales/ru.json index d50e3733..31eb29cb 100644 --- a/src/i18n/locales/ru.json +++ b/src/i18n/locales/ru.json @@ -2727,6 +2727,7 @@ "cloudNotSignedIn": "Войдите в аккаунт Donut Browser, чтобы использовать эту функцию.", "cloudUnreachable": "Не удалось связаться с серверами Donut Browser. Проверьте подключение и попробуйте снова.", "cloudRequestFailed": "Запрос не удался. Попробуйте ещё раз через минуту.", + "cloudBrowserUpdating": "Облачный браузер обновляется до вашей версии Wayfern. Повторите попытку через несколько минут.", "remoteRateLimited": "Слишком много запросов. Подождите немного и попробуйте снова.", "remoteNoCapacity": "Сейчас нет свободных удалённых машин. Попробуйте через несколько минут.", "remoteNotEntitled": "Ваш тариф не включает удалённый запуск.", @@ -3799,6 +3800,7 @@ "quotaExhausted": "Удалённые часы израсходованы", "profileLocked": "Профиль был открыт в другом месте", "noCapacity": "Свободных машин не было", + "cloudBrowserUpdating": "Облачный браузер обновлялся до вашей версии Wayfern", "managerError": "Флот не смог запустить браузер", "budgetExceeded": "Отведённое на запуск время закончилось", "browsingIncomplete": "Браузер открылся, но обход сайтов не завершился", diff --git a/src/i18n/locales/tr.json b/src/i18n/locales/tr.json index 7094f410..5d39bdd8 100644 --- a/src/i18n/locales/tr.json +++ b/src/i18n/locales/tr.json @@ -2689,6 +2689,7 @@ "cloudNotSignedIn": "Bunu kullanmak için Donut Browser hesabınıza giriş yapın.", "cloudUnreachable": "Donut Browser sunucularına ulaşılamadı. Bağlantınızı kontrol edip tekrar deneyin.", "cloudRequestFailed": "İstek başarısız oldu. Birazdan tekrar deneyin.", + "cloudBrowserUpdating": "Bulut tarayıcısı Wayfern sürümünüze güncelleniyor. Birkaç dakika sonra tekrar deneyin.", "remoteRateLimited": "Çok fazla istek. Biraz bekleyip tekrar deneyin.", "remoteNoCapacity": "Şu anda boş uzak makine yok. Birkaç dakika sonra tekrar deneyin.", "remoteNotEntitled": "Planınız uzaktan çalıştırmayı içermiyor.", @@ -3721,6 +3722,7 @@ "quotaExhausted": "Uzak saatler tükendi", "profileLocked": "Profil başka bir yerde açıktı", "noCapacity": "Boş makine yoktu", + "cloudBrowserUpdating": "Bulut tarayıcısı Wayfern sürümünüze güncelleniyordu", "managerError": "Filo tarayıcıyı başlatamadı", "budgetExceeded": "Çalışmanın süresi doldu", "browsingIncomplete": "Tarayıcı açıldı ama gezinme tamamlanmadı", diff --git a/src/i18n/locales/vi.json b/src/i18n/locales/vi.json index 9c35004c..cb793d26 100644 --- a/src/i18n/locales/vi.json +++ b/src/i18n/locales/vi.json @@ -2689,6 +2689,7 @@ "cloudNotSignedIn": "Hãy đăng nhập tài khoản Donut Browser để dùng tính năng này.", "cloudUnreachable": "Không kết nối được tới máy chủ Donut Browser. Hãy kiểm tra kết nối và thử lại.", "cloudRequestFailed": "Yêu cầu thất bại. Hãy thử lại sau giây lát.", + "cloudBrowserUpdating": "Trình duyệt đám mây đang cập nhật lên phiên bản Wayfern của bạn. Hãy thử lại sau vài phút.", "remoteRateLimited": "Quá nhiều yêu cầu. Hãy đợi một lát rồi thử lại.", "remoteNoCapacity": "Hiện không có máy từ xa nào rảnh. Hãy thử lại sau vài phút.", "remoteNotEntitled": "Gói của bạn không bao gồm chạy từ xa.", @@ -3721,6 +3722,7 @@ "quotaExhausted": "Đã dùng hết số giờ từ xa", "profileLocked": "Hồ sơ đang mở ở nơi khác", "noCapacity": "Không có máy nào rảnh", + "cloudBrowserUpdating": "Trình duyệt đám mây đang cập nhật lên phiên bản Wayfern của bạn", "managerError": "Cụm máy không khởi động được trình duyệt", "budgetExceeded": "Đã hết thời lượng dành cho lần chạy", "browsingIncomplete": "Trình duyệt đã mở nhưng phiên duyệt chưa hoàn tất", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index 8a0c10e3..cc1f0fda 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -2689,6 +2689,7 @@ "cloudNotSignedIn": "请先登录你的 Donut Browser 账号再使用此功能。", "cloudUnreachable": "无法连接 Donut Browser 服务器。请检查网络后重试。", "cloudRequestFailed": "请求失败。请稍后再试。", + "cloudBrowserUpdating": "云端浏览器正在更新到你的 Wayfern 版本。请几分钟后再试。", "remoteRateLimited": "请求过于频繁。请稍候再试。", "remoteNoCapacity": "当前没有空闲的远程机器。请几分钟后再试。", "remoteNotEntitled": "你的套餐不包含远程运行。", @@ -3721,6 +3722,7 @@ "quotaExhausted": "远程时长已用完", "profileLocked": "该配置文件已在别处打开", "noCapacity": "没有空闲机器", + "cloudBrowserUpdating": "云端浏览器当时正在更新到你的 Wayfern 版本", "managerError": "机器集群未能启动浏览器", "budgetExceeded": "本次运行的时长预算已用完", "browsingIncomplete": "浏览器已启动,但浏览未完成", diff --git a/src/lib/backend-errors.ts b/src/lib/backend-errors.ts index 4c9181eb..be4deef4 100644 --- a/src/lib/backend-errors.ts +++ b/src/lib/backend-errors.ts @@ -190,6 +190,7 @@ export type BackendErrorCode = | "CLOUD_NOT_SIGNED_IN" | "CLOUD_UNREACHABLE" | "CLOUD_REQUEST_FAILED" + | "CLOUD_BROWSER_UPDATING" | "REMOTE_RATE_LIMITED" | "REMOTE_NO_CAPACITY" // The deployment has no fleet credential at all, which is not the same as a @@ -949,6 +950,8 @@ export function translateBackendError(t: TFunction, err: unknown): string { return t("backendErrors.cloudUnreachable"); case "CLOUD_REQUEST_FAILED": return t("backendErrors.cloudRequestFailed"); + case "CLOUD_BROWSER_UPDATING": + return t("backendErrors.cloudBrowserUpdating"); case "REMOTE_RATE_LIMITED": return t("backendErrors.remoteRateLimited"); case "REMOTE_NO_CAPACITY": { diff --git a/src/lib/cookie-bot-outcomes.test.mjs b/src/lib/cookie-bot-outcomes.test.mjs index ee8dc8fa..300f2e62 100644 --- a/src/lib/cookie-bot-outcomes.test.mjs +++ b/src/lib/cookie-bot-outcomes.test.mjs @@ -42,6 +42,7 @@ const SERVER_OUTCOME_CODES = [ "browsing_incomplete", "budget_exceeded", "cancelled_by_user", + "cloud_browser_updating", "encrypted_sync", "manager_error", "no_capacity",