Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions gateway/platforms/whatsapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,11 +260,15 @@ class WhatsAppAdapter(WhatsAppBehaviorMixin, BasePlatformAdapter):
share it. Only transport-specific code lives here.
"""

# Default bridge location relative to the hermes-agent install
_DEFAULT_BRIDGE_DIR = Path(__file__).resolve().parents[2] / "scripts" / "whatsapp-bridge"
# Default bridge location resolved via shared helper
_DEFAULT_BRIDGE_DIR = None # resolved in __init__

def __init__(self, config: PlatformConfig):
super().__init__(config, Platform.WHATSAPP)
# Use shared helper for bridge directory resolution (handles read-only install tree)
if WhatsAppAdapter._DEFAULT_BRIDGE_DIR is None:
from gateway.platforms.whatsapp_common import resolve_whatsapp_bridge_dir
WhatsAppAdapter._DEFAULT_BRIDGE_DIR = resolve_whatsapp_bridge_dir()
self._bridge_process: Optional[subprocess.Popen] = None
self._bridge_port: int = config.extra.get("bridge_port", 3000)
self._bridge_script: Optional[str] = config.extra.get(
Expand Down
53 changes: 53 additions & 0 deletions gateway/platforms/whatsapp_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,3 +365,56 @@ def _header_to_bold(m: re.Match) -> str:
result = result.replace(f"{_CODE_PH}{i}\x00", code)

return result


# ---------------------------------------------------------------------------
# Shared bridge directory resolution for CLI and adapter
# ---------------------------------------------------------------------------

def resolve_whatsapp_bridge_dir() -> Path:
"""Resolve the WhatsApp bridge directory, mirroring to HERMES_HOME if needed.

When the install tree is read-only (e.g., Docker /opt/hermes), this function
mirrors the bridge source to a writable HERMES_HOME location and returns that
path. This ensures npm install works in Docker environments.

Returns the resolved bridge directory path.
"""
import shutil
from pathlib import Path as _Path

# Default location in install tree (may be read-only)
from hermes_constants import get_hermes_home
install_bridge = _Path(__file__).resolve().parents[2] / "scripts" / "whatsapp-bridge"

# Try HERMES_HOME location first
hermes_home = get_hermes_home()
hermes_home_bridge = hermes_home / "scripts" / "whatsapp-bridge"

# Check if install dir is writable
try:
test_file = install_bridge / ".write_test"
test_file.touch()
test_file.unlink()
install_writable = True
except (OSError, PermissionError):
install_writable = False

if install_writable:
return install_bridge

# Install dir is read-only, mirror to HERMES_HOME if needed
if hermes_home_bridge.exists():
return hermes_home_bridge

# Mirror the bridge source to HERMES_HOME
try:
hermes_home_bridge.parent.mkdir(parents=True, exist_ok=True)
shutil.copytree(
install_bridge,
hermes_home_bridge,
dirs_exist_ok=False,
)
return hermes_home_bridge
except Exception:
return install_bridge
4 changes: 2 additions & 2 deletions hermes_cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2466,8 +2466,8 @@ def cmd_whatsapp(args):
print(" ⚠ No allowlist — the agent will respond to ALL incoming messages")

# ── Step 4: Install bridge dependencies ──────────────────────────────
project_root = Path(__file__).resolve().parents[1]
bridge_dir = project_root / "scripts" / "whatsapp-bridge"
from gateway.platforms.whatsapp_common import resolve_whatsapp_bridge_dir
bridge_dir = resolve_whatsapp_bridge_dir()
bridge_script = bridge_dir / "bridge.js"

if not bridge_script.exists():
Expand Down