From 46f234f4fab4fdc67e1688a8bbeeed612bf4d3fa Mon Sep 17 00:00:00 2001 From: nesquena-hermes Date: Fri, 19 Jun 2026 03:14:08 +0000 Subject: [PATCH] =?UTF-8?q?release:=20v0.51.509=20=E2=80=94=20notice=20whe?= =?UTF-8?q?n=20connected=20gateway=20can't=20do=20approvals=20(#4456,=20#4?= =?UTF-8?q?300)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Includes a showToast arg-order fix (4000ms,'warning') caught at gate. Co-authored-by: rodboev Closes #4456 --- CHANGELOG.md | 6 ++ api/gateway_chat.py | 12 ++++ static/i18n.js | 13 ++++ static/messages.js | 4 ++ .../test_issue4300_gateway_approval_notice.py | 68 +++++++++++++++++++ 5 files changed, 103 insertions(+) create mode 100644 tests/test_issue4300_gateway_approval_notice.py diff --git a/CHANGELOG.md b/CHANGELOG.md index e0464e51c..938bfd847 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ ## [Unreleased] +## [v0.51.509] — 2026-06-19 — Release RT (notice when the connected gateway can't do approvals) + +### Added + +- **The chat now shows a one-time notice when the connected Hermes gateway is too old to support command approvals (#4300).** On the legacy gateway path, if the gateway lacks approval capability, a single per-session warning ("Approvals require a newer gateway — upgrade the connected Hermes gateway to enable this") is surfaced instead of approvals silently doing nothing. Localized in all bundled locales. Thanks @rodboev. + ## [v0.51.508] — 2026-06-19 — Release RS (the /model command works under a reverse-proxy subpath) ### Fixed diff --git a/api/gateway_chat.py b/api/gateway_chat.py index 1b232411e..c4cca5764 100644 --- a/api/gateway_chat.py +++ b/api/gateway_chat.py @@ -584,6 +584,18 @@ def _run_gateway_chat_streaming( if final_text is None: return else: + # Legacy gateway path: emit unsupported approval notice once per session, + # but only when the gateway genuinely lacks approval capability. + if not gateway_supports_approval(base_url, api_key): + if not hasattr(s, "_approval_notice_emitted"): + s._approval_notice_emitted = False + if not s._approval_notice_emitted: + put_gateway_event("warning", { + "type": "approval_gateway_unsupported", + "message": "Approvals require a newer gateway. Upgrade the connected Hermes gateway to enable this.", + }) + s._approval_notice_emitted = True + url = f"{base_url}/v1/chat/completions" headers = { "Content-Type": "application/json", diff --git a/static/i18n.js b/static/i18n.js index d646c6cca..43bd84178 100644 --- a/static/i18n.js +++ b/static/i18n.js @@ -156,6 +156,7 @@ const LOCALES = { approval_btn_deny_title: 'Deny — do not run this command', approval_responding: 'Responding\u2026', approval_gateway_unsupported: 'Approvals require a newer gateway. Upgrade the connected Hermes gateway to enable this.', + approval_gateway_unsupported_label: 'Approvals not supported', clarify_heading: 'Clarification needed', clarify_hint: 'Pick a choice, or type your own answer below.', clarify_other: 'Other', @@ -1668,6 +1669,7 @@ const LOCALES = { approval_btn_deny_title: 'Nega — non eseguire questo comando', approval_responding: 'Rispondendo\u2026', approval_gateway_unsupported: 'Approvals require a newer gateway. Upgrade the connected Hermes gateway to enable this.', + approval_gateway_unsupported_label: 'Approvazioni non supportate', clarify_heading: 'Chiarimento necessario', clarify_hint: "Scegli un'opzione o scrivi la tua risposta qui sotto.", clarify_other: 'Altro', @@ -3171,6 +3173,7 @@ const LOCALES = { approval_btn_deny_title: '拒否 — このコマンドを実行しない', approval_responding: '応答中…', approval_gateway_unsupported: '承認を実行するには新しいゲートウェイが必要です。有効にするには接続されている Hermes ゲートウェイをアップグレードしてください。', + approval_gateway_unsupported_label: '承認はサポートされていません', clarify_heading: '確認が必要', clarify_hint: '選択肢を選ぶか、下に独自の回答を入力してください。', clarify_other: 'その他', @@ -4662,6 +4665,7 @@ const LOCALES = { approval_btn_deny_title: 'Запретить — не выполнять эту команду', approval_responding: 'Отвечаю…', approval_gateway_unsupported: 'Approvals require a newer gateway. Upgrade the connected Hermes gateway to enable this.', + approval_gateway_unsupported_label: 'Одобрение не поддерживается', untitled: 'Без названия', n_messages: (n) => `${n} сообщений`, load_older_messages: '↑ Прокрутите вверх или нажмите, чтобы загрузить ранние сообщения', @@ -6098,6 +6102,7 @@ const LOCALES = { approval_btn_deny_title: 'Denegar — no ejecutar este comando', approval_responding: 'Respondiendo…', approval_gateway_unsupported: 'Approvals require a newer gateway. Upgrade the connected Hermes gateway to enable this.', + approval_gateway_unsupported_label: 'Las aprobaciones no son compatibles', clarify_heading: 'Se necesita aclaración', clarify_hint: 'Elige una opción o escribe tu propia respuesta abajo.', clarify_other: 'Otra', @@ -7537,6 +7542,7 @@ const LOCALES = { approval_btn_deny_title: 'Ablehnen \u2014 diesen Befehl nicht ausführen', approval_responding: 'Antwortet\u2026', approval_gateway_unsupported: 'Approvals require a newer gateway. Upgrade the connected Hermes gateway to enable this.', + approval_gateway_unsupported_label: 'Genehmigungen nicht unterstützt', clarify_heading: 'Klärung erforderlich', clarify_hint: 'Wähle eine Option oder schreibe deine eigene Antwort unten.', clarify_other: 'Andere', @@ -8980,6 +8986,7 @@ const LOCALES = { approval_btn_deny_title: '拒绝 — 不执行此命令', approval_responding: '处理中…', approval_gateway_unsupported: 'Approvals require a newer gateway. Upgrade the connected Hermes gateway to enable this.', + approval_gateway_unsupported_label: '不支持批准', clarify_heading: '需要澄清', clarify_hint: '请选择一个选项,或在下方输入你自己的回答。', clarify_other: '其他', @@ -10444,6 +10451,7 @@ const LOCALES = { approval_btn_deny_title: '拒絕,不要執行這道命令', approval_responding: '正在回應……', approval_gateway_unsupported: 'Approvals require a newer gateway. Upgrade the connected Hermes gateway to enable this.', + approval_gateway_unsupported_label: '不支援核准', clarify_heading: '需要釐清', clarify_hint: '請選擇一個選項,或在下方輸入回答。', clarify_other: '其他', @@ -11857,6 +11865,7 @@ const LOCALES = { approval_btn_deny_title: 'Negar — não executar este comando', approval_responding: 'Respondendo…', approval_gateway_unsupported: 'Approvals require a newer gateway. Upgrade the connected Hermes gateway to enable this.', + approval_gateway_unsupported_label: 'Aprovações não suportadas', clarify_heading: 'Esclarecimento necessário', clarify_hint: 'Escolha uma opção ou digite sua resposta abaixo.', clarify_other: 'Outro', @@ -13246,6 +13255,7 @@ const LOCALES = { approval_btn_deny_title: '거부 — 이 명령을 실행하지 않음', approval_responding: '응답 중\u2026', approval_gateway_unsupported: 'Approvals require a newer gateway. Upgrade the connected Hermes gateway to enable this.', + approval_gateway_unsupported_label: '승인이 지원되지 않음', clarify_heading: '확인 필요', clarify_hint: '선택지를 고르거나 아래에 직접 입력하세요.', clarify_other: '기타', @@ -14757,6 +14767,7 @@ const LOCALES = { approval_btn_deny_title: 'Refuser - n\'exécutez pas cette commande', approval_responding: 'Répondre\u2026', approval_gateway_unsupported: 'Approvals require a newer gateway. Upgrade the connected Hermes gateway to enable this.', + approval_gateway_unsupported_label: 'Les approbations ne sont pas prises en charge', clarify_heading: 'Des éclaircissements sont nécessaires', clarify_hint: 'Choisissez un choix ou saisissez votre propre réponse ci-dessous.', clarify_other: 'Autre', @@ -16196,6 +16207,7 @@ const LOCALES = { approval_btn_deny_title: 'Reddet — bu komutu çalıştırma', approval_responding: 'Yanıt veriliyor\u2026', approval_gateway_unsupported: 'Approvals require a newer gateway. Upgrade the connected Hermes gateway to enable this.', + approval_gateway_unsupported_label: 'Onaylar desteklenmiyor', clarify_heading: 'Açıklama gerekli', clarify_hint: 'Bir seçim yapın veya kendi cevabınızı aşağıya yazın.', clarify_other: 'Diğer', @@ -17701,6 +17713,7 @@ const LOCALES = { approval_btn_deny_title: 'Odmów — nie uruchamiaj tego polecenia', approval_responding: 'Odpowiadanie…', approval_gateway_unsupported: 'Approvals require a newer gateway. Upgrade the connected Hermes gateway to enable this.', + approval_gateway_unsupported_label: 'Zatwierdzenia nie są obsługiwane', clarify_heading: 'Wymagane wyjaśnienie', clarify_hint: 'Wybierz opcję lub wpisz własną odpowiedź poniżej.', clarify_other: 'Inne', diff --git a/static/messages.js b/static/messages.js index f0fba24f0..de6a81b2b 100644 --- a/static/messages.js +++ b/static/messages.js @@ -3759,6 +3759,10 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){ if(!S.session||S.session.session_id!==activeSid) return; try{ const d=JSON.parse(e.data); + if(d.type==='approval_gateway_unsupported'){ + if(typeof showToast==='function') showToast(typeof t==='function'?t('approval_gateway_unsupported_label'):'Approvals not supported',4000,'warning'); + return; + } // Show as a small inline notice, not a full error setComposerStatus(`${d.message||'Warning'}`); // If it's a fallback notice, show it briefly then clear diff --git a/tests/test_issue4300_gateway_approval_notice.py b/tests/test_issue4300_gateway_approval_notice.py new file mode 100644 index 000000000..0ffc71dbe --- /dev/null +++ b/tests/test_issue4300_gateway_approval_notice.py @@ -0,0 +1,68 @@ +"""Regression coverage for #4300 legacy gateway approval unsupported notice.""" + +from pathlib import Path + +REPO = Path(__file__).resolve().parents[1] +GATEWAY_CHAT = (REPO / "api" / "gateway_chat.py").read_text(encoding="utf-8") +MESSAGES_JS = (REPO / "static" / "messages.js").read_text(encoding="utf-8") +I18N_JS = (REPO / "static" / "i18n.js").read_text(encoding="utf-8") + + +def test_gateway_chat_has_approval_notice_emitted_attribute_check(): + """Verify _approval_notice_emitted attribute is checked before emitting.""" + assert "if not hasattr(s, \"_approval_notice_emitted\"):" in GATEWAY_CHAT + assert "s._approval_notice_emitted = False" in GATEWAY_CHAT + + +def test_gateway_chat_emits_approval_gateway_unsupported_event(): + """Verify put_gateway_event is called with approval_gateway_unsupported type on non-terminal channel.""" + assert "put_gateway_event(\"warning\"" in GATEWAY_CHAT + assert "\"type\": \"approval_gateway_unsupported\"" in GATEWAY_CHAT + + +def test_gateway_chat_once_per_session_guard_pattern(): + """Verify the once-per-session guard: capability check + hasattr + flag check + flag set.""" + assert "if not gateway_supports_approval(base_url, api_key):" in GATEWAY_CHAT + assert "if not hasattr(s, \"_approval_notice_emitted\"):" in GATEWAY_CHAT + assert "if not s._approval_notice_emitted:" in GATEWAY_CHAT + assert "s._approval_notice_emitted = True" in GATEWAY_CHAT + # Verify order: capability gate before session guard before flag set + cap_pos = GATEWAY_CHAT.find("if not gateway_supports_approval(base_url, api_key):") + hasattr_pos = GATEWAY_CHAT.find("if not hasattr(s, \"_approval_notice_emitted\"):") + flag_check_pos = GATEWAY_CHAT.find("if not s._approval_notice_emitted:") + flag_set_pos = GATEWAY_CHAT.find("s._approval_notice_emitted = True") + assert cap_pos < hasattr_pos < flag_check_pos < flag_set_pos + + +def test_gateway_chat_event_payload_contains_type_and_message(): + """Verify the event payload has type and message fields.""" + assert "\"type\": \"approval_gateway_unsupported\"" in GATEWAY_CHAT + assert "\"message\": \"Approvals require a newer gateway. Upgrade the connected Hermes gateway to enable this.\"" in GATEWAY_CHAT + + +def test_messages_js_handles_approval_gateway_unsupported_event(): + """Verify client-side warning handler recognizes the event type.""" + assert "d.type==='approval_gateway_unsupported'" in MESSAGES_JS + + +def test_messages_js_references_i18n_key_for_approval_gateway_unsupported(): + """Verify the i18n key is referenced in messages.js.""" + # The key should be used in the message handling logic + assert "approval_gateway_unsupported" in MESSAGES_JS + + +def test_i18n_js_has_approval_gateway_unsupported_key(): + """Verify the i18n key exists in at least the English locale (first occurrence).""" + assert "approval_gateway_unsupported: 'Approvals require a newer gateway" in I18N_JS + lines = I18N_JS.split("\n") + en_end = next(i for i, l in enumerate(lines) if l.strip().startswith("zh:")) + found_in_english = any( + "approval_gateway_unsupported:" in l + for l in lines[:en_end] + ) + assert found_in_english, "approval_gateway_unsupported key not found in English i18n locale" + + +if __name__ == "__main__": + import pytest + pytest.main([__file__, "-v"])