release: v0.51.509 — notice when connected gateway can't do approvals (#4456, #4300)

Includes a showToast arg-order fix (4000ms,'warning') caught at gate.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>

Closes #4456
This commit is contained in:
nesquena-hermes
2026-06-19 03:14:08 +00:00
parent 0ebb1e2ea7
commit 46f234f4fa
5 changed files with 103 additions and 0 deletions
+6
View File
@@ -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
+12
View File
@@ -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",
+13
View File
@@ -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',
+4
View File
@@ -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
@@ -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"])