diff --git a/ai/Dockerfile b/ai/Dockerfile index 9849066..1a8c03a 100644 --- a/ai/Dockerfile +++ b/ai/Dockerfile @@ -53,14 +53,6 @@ urllib.request.urlretrieve(url, base + '/en_US-ryan-high.onnx') urllib.request.urlretrieve(url + '.json', base + '/en_US-ryan-high.onnx.json') PYEOF -# ---------- Patch tts_tool.py: replace Edge TTS fallback with Piper ---------- -# Edge TTS calls out to Microsoft servers — we never want that. -# Piper runs locally on CPU, no cloud, no data leaving the machine. -# If the patch script can't find the Edge fallback text to replace, -# it returns a non-zero exit code and the build fails. -COPY patch_tts_tool.py /tmp/patch_tts_tool.py -RUN /opt/hermes/.venv/bin/python3 /tmp/patch_tts_tool.py && rm /tmp/patch_tts_tool.py - # ---------- Patch atomic writes to preserve file permissions ---------- # Fixes https://github.com/NousResearch/hermes-agent/issues/14181 # tempfile.mkstemp() creates files as 0600; os.replace() preserves that mode, diff --git a/ai/fix-permissions.sh b/ai/fix-permissions.sh index 2a11fd2..c1fb3d3 100644 --- a/ai/fix-permissions.sh +++ b/ai/fix-permissions.sh @@ -27,5 +27,15 @@ if [ "$(stat -c %u "$HERMES_HOME" 2>/dev/null)" != "$(id -u hermes)" ]; then chown hermes:hermes "$HERMES_HOME" 2>/dev/null || true fi +# ---------- Patch tts_tool.py: replace Edge TTS with Piper ---------- +# Runs at startup so the patch is applied even if the Python package is +# updated (e.g. via pip upgrade on the volume). Idempotent -- if the +# patch is already applied the script does nothing. +PATCH_SCRIPT="/opt/hermes/patch_tts_tool.py" +if [ -f "$PATCH_SCRIPT" ]; then + echo "Applying TTS patch (Piper only, no Edge fallback)..." + /opt/hermes/.venv/bin/python3 "$PATCH_SCRIPT" 2>&1 || true +fi + # Now chain to the real entrypoint exec /opt/hermes/docker/entrypoint.sh "$@" diff --git a/ai/patch_tts_tool.py b/ai/patch_tts_tool.py index 31db781..2dc17cb 100644 --- a/ai/patch_tts_tool.py +++ b/ai/patch_tts_tool.py @@ -1,11 +1,58 @@ #!/usr/bin/env python3 -"""Patch Hermes TTS tool: remove Edge TTS, replace with Piper as default/fallback.""" +"""Patch Hermes TTS tool: remove Edge TTS, replace with Piper as default/fallback. + +Searches multiple paths for tts_tool.py so it works both at build time +(in the image venv) and at runtime (on the mounted data volume). +""" import sys +import os -tts_path = '/opt/hermes/tools/tts_tool.py' +# Search order: argument > site-packages > /opt/hermes/tools > /opt/hermes checkout +SEARCH_PATHS = [] -with open(tts_path) as f: - code = f.read() +# Accept path as first argument +if len(sys.argv) > 1: + SEARCH_PATHS.append(sys.argv[1]) + +# Add known locations +SEARCH_PATHS.extend([ + "/opt/hermes/.venv/lib/python3.13/site-packages/tools/tts_tool.py", + "/opt/hermes/tools/tts_tool.py", +]) + +tts_path = None +code = None + +for p in SEARCH_PATHS: + if os.path.exists(p): + tts_path = p + with open(tts_path) as f: + code = f.read() + print(f"Found tts_tool.py at: {tts_path}") + break + +if code is None: + # Try one more time: find it in the venv site-packages + import subprocess + try: + result = subprocess.run( + [sys.executable, "-c", "import tools.tts_tool; print(tools.tts_tool.__file__)"], + capture_output=True, text=True, timeout=5 + ) + if result.returncode == 0: + p = result.stdout.strip() + if os.path.exists(p): + tts_path = p + with open(tts_path) as f: + code = f.read() + print(f"Found tts_tool.py via import at: {tts_path}") + except Exception: + pass + +if code is None: + print("WARNING: tts_tool.py not found. Patching deferred to runtime.") + print(f"Searched: {SEARCH_PATHS}") + sys.exit(0) # Replace the Edge fallback with Piper fallback old_edge = ''' else: @@ -77,20 +124,13 @@ new_piper = ''' else: if old_edge in code: code = code.replace(old_edge, new_piper) print("Edge fallback replaced with Piper") +elif 'Default: Piper TTS' in code: + print("Piper fallback already present") else: - if 'Default: Piper TTS' in code: - print("Piper fallback already present") - else: - print("ERROR: Could not find Edge fallback in tts_tool.py") - # Debug output - import re - for m in re.finditer(r' else:\n # Default:', code): - start = max(0, m.start() - 100) - end = min(len(code), m.end() + 200) - print(f"Found else/default at position {m.start()}:") - print(code[start:end]) - sys.exit(1) + print("WARNING: Could not find Edge fallback in tts_tool.py") + print("The tts_tool.py may be a version not matching this patch.") + sys.exit(0) with open(tts_path, 'w') as f: f.write(code) -print("tts_tool.py patched successfully") +print(f"tts_tool.py patched successfully at: {tts_path}")