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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ source-data/*
run_datagen_megascience_glm4-6.sh
data/*
node_modules/
package-lock.json
browser-use/
agent-browser/
# Private keys
Expand Down
73 changes: 53 additions & 20 deletions docs/messaging.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,29 +134,62 @@ pip install discord.py>=2.0

### WhatsApp

WhatsApp integration is more complex due to the lack of a simple bot API.
WhatsApp integration uses a Node.js bridge process that connects to WhatsApp Web
via [Baileys](https://github.com/WhiskeySockets/Baileys) (no browser/Chromium needed).

**Options:**
1. **WhatsApp Business API** (requires Meta verification)
2. **whatsapp-web.js** via Node.js bridge (for personal accounts)
**Requirements:**
- Node.js >= 18

**Bridge Setup:**
1. Install Node.js
2. Set up the bridge script (see `scripts/whatsapp-bridge/` for reference)
3. Configure in gateway:
```json
{
"platforms": {
"whatsapp": {
"enabled": true,
"extra": {
"bridge_script": "/path/to/bridge.js",
"bridge_port": 3000
}
}
}
}
```

```bash
# 1. Install bridge dependencies
cd scripts/whatsapp-bridge
npm install

# 2. First run - scan the QR code printed in terminal with your phone
node bridge.js --port 3000

# 3. After scanning, the session is saved automatically.
# Subsequent launches will reconnect without a QR code.
```

**Gateway Configuration:**

Add to `~/.hermes/gateway.json`:

```json
{
"platforms": {
"whatsapp": {
"enabled": true,
"extra": {
"bridge_script": "/absolute/path/to/scripts/whatsapp-bridge/bridge.js",
"bridge_port": 3000
}
}
}
}
```

Or set environment variables in `~/.hermes/.env`:

```bash
WHATSAPP_ENABLED=true
```

**How it works:**

The gateway launches the bridge as a subprocess (`node bridge.js --port 3000 --session <path>`).
The bridge connects to WhatsApp Web, buffers incoming messages, and exposes HTTP endpoints
that the Python adapter polls. Media files (images, voice notes, documents) are downloaded
and served via a local HTTP endpoint.

**Notes:**
- The first time you run the bridge, scan the QR code with WhatsApp on your phone
- Session data is stored in `~/.hermes/whatsapp/session/` by default
- If you get logged out, delete the session directory and re-authenticate
- The bridge auto-reconnects on temporary disconnections

## Configuration

Expand Down
46 changes: 29 additions & 17 deletions gateway/platforms/whatsapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,38 +115,50 @@ async def connect(self) -> bool:
try:
# Ensure session directory exists
self._session_path.mkdir(parents=True, exist_ok=True)

# Start the bridge process

# Start the bridge process with inherited stdout/stderr so the
# QR code printed by the bridge is visible to the user.
self._bridge_process = subprocess.Popen(
[
"node",
str(bridge_path),
"--port", str(self._bridge_port),
"--session", str(self._session_path),
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)

# Wait for bridge to be ready (look for ready signal)
# This is a simplified version - real implementation would
# wait for an HTTP health check or specific stdout message
await asyncio.sleep(5)

if self._bridge_process.poll() is not None:
stderr = self._bridge_process.stderr.read() if self._bridge_process.stderr else ""
print(f"[{self.name}] Bridge process died: {stderr}")

# Poll the bridge health endpoint until it responds (up to 30s).
import aiohttp
ready = False
for _ in range(30):
await asyncio.sleep(1)
if self._bridge_process.poll() is not None:
print(f"[{self.name}] Bridge process exited unexpectedly.")
return False
try:
async with aiohttp.ClientSession() as session:
async with session.get(
f"http://localhost:{self._bridge_port}/health",
timeout=aiohttp.ClientTimeout(total=2)
) as resp:
if resp.status == 200:
ready = True
break
except Exception:
pass

if not ready:
print(f"[{self.name}] Bridge did not become ready within 30s.")
return False

# Start message polling task
asyncio.create_task(self._poll_messages())

self._running = True
print(f"[{self.name}] Bridge started on port {self._bridge_port}")
print(f"[{self.name}] Scan QR code if prompted (check bridge output)")
return True

except Exception as e:
print(f"[{self.name}] Failed to start bridge: {e}")
return False
Expand Down
Loading