From 086dd5cde7465f636116139e381b3af86df32905 Mon Sep 17 00:00:00 2001 From: javis-bot Date: Tue, 23 Jun 2026 03:49:53 +0900 Subject: [PATCH] fix: accept edge as a valid tts_engine and migrate stale persisted engines load_settings() coerced any tts_engine outside {piper, chatterbox} to piper, so with TTS_ENGINE=edge the reply engine saw "piper" and treated the voice as English-only in reply_language_directive() (only the OUTPUT_LANGUAGE lock kept replies Korean). Add "edge" (and "melo") to the accepted set so the engine is labelled multilingual correctly. Also: a stale tts_engine in the persistent /data/jarvis-settings.json (melo/xtts from an earlier voice, no longer built) would override the configured engine via the entrypoint merge and leave the bot silent. Reset those to the env engine during the merge. Verified: load_settings() with tts_engine=edge now returns "edge"; the merge maps melo/xtts -> edge; reply_language_directive("edge") is multilingual; 27 tests pass. Co-Authored-By: Claude Opus 4.7 --- docker/entrypoint.sh | 8 +++++++- src/jarvis/config.py | 6 +++++- tests/test_system_prompt.py | 8 ++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 84e3e53..22bab02 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -51,12 +51,18 @@ export JARVIS_CONFIG_PATH=/app/config/jarvis.json # the env-rendered config, so changes survive container recreate. if [ -f /data/jarvis-settings.json ]; then python3 - <<'PY' || true -import json +import json, os try: base = json.load(open("/app/config/jarvis.json")) ov = json.load(open("/data/jarvis-settings.json")) if isinstance(base, dict) and isinstance(ov, dict): base.update(ov) + # A stale persisted tts_engine from an earlier voice (melo/xtts, no + # longer built into the image) would override the configured engine and + # leave the bot silent. Reset those to the env-configured engine. + if base.get("tts_engine") in ("melo", "xtts"): + base["tts_engine"] = os.environ.get("TTS_ENGINE", "edge") + print(f"[entrypoint] reset stale tts_engine -> {base['tts_engine']}") json.dump(base, open("/app/config/jarvis.json", "w"), ensure_ascii=False, indent=2) print("[entrypoint] merged persistent settings overrides") except Exception as e: diff --git a/src/jarvis/config.py b/src/jarvis/config.py index cbc61f1..854861f 100644 --- a/src/jarvis/config.py +++ b/src/jarvis/config.py @@ -608,7 +608,11 @@ def load_settings() -> Settings: active_profiles = _ensure_list(merged.get("active_profiles")) tts_enabled = bool(merged.get("tts_enabled", True)) tts_engine = str(merged.get("tts_engine", "piper")).lower() - if tts_engine not in ("piper", "chatterbox"): + # "edge" (Microsoft Edge TTS) is the containerized bridge's Korean voice; + # "melo" is the legacy warm-worker voice. Both are multilingual, so they must + # be preserved here — coercing them to "piper" would mislabel the engine as + # English-only in reply_language_directive(). + if tts_engine not in ("piper", "chatterbox", "edge", "melo"): tts_engine = "piper" # Default to piper if invalid value tts_voice_val = merged.get("tts_voice") tts_voice = None if tts_voice_val in (None, "", "null") else str(tts_voice_val) diff --git a/tests/test_system_prompt.py b/tests/test_system_prompt.py index 3134989..0b497c4 100644 --- a/tests/test_system_prompt.py +++ b/tests/test_system_prompt.py @@ -110,6 +110,14 @@ class TestReplyLanguageDirective: directive = reply_language_directive("Korean", "melo") assert directive is not None and "Korean" in directive + def test_edge_is_multilingual(self): + # Edge TTS (the default Korean voice) is not English-only: no lock → the + # user's own language, and a lock is honoured (not forced to English). + assert reply_language_directive(None, "edge") is None + directive = reply_language_directive("Korean", "edge") + assert directive is not None and "Korean" in directive + assert directive != ENGLISH_ONLY_DIRECTIVE + class TestLoadAgentInstructions: """Operator can extend the reply LLM's system prompt by dropping *.md files