Skip to content

feat: add WhatsApp Web bridge via Baileys#36

Closed
0xbyt4 wants to merge 2 commits into
NousResearch:mainfrom
0xbyt4:feat/whatsapp-bridge
Closed

feat: add WhatsApp Web bridge via Baileys#36
0xbyt4 wants to merge 2 commits into
NousResearch:mainfrom
0xbyt4:feat/whatsapp-bridge

Conversation

@0xbyt4

@0xbyt4 0xbyt4 commented Feb 26, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add Node.js bridge script (scripts/whatsapp-bridge/) that connects to WhatsApp Web using Baileys (no browser/Chromium needed)
  • Fix Python adapter (gateway/platforms/whatsapp.py): inherit stdout/stderr so QR code is visible, replace fixed 5s sleep with /health endpoint polling
  • Update docs/messaging.md with concrete WhatsApp setup steps

Bridge HTTP Endpoints

Endpoint Description
GET /messages Poll incoming messages (returns & clears queue)
POST /send Send message { chatId, message, replyTo? }
POST /typing Send typing indicator { chatId }
GET /chat/:chatId Chat info (group metadata, DM name)
GET /health Connection status
GET /media/:file Serve downloaded media files

How it works

WhatsApp Web <--Baileys--> bridge.js (Express HTTP :3000) <--polling--> whatsapp.py adapter
  • Bridge buffers incoming messages, Python adapter polls GET /messages every 1s
  • Media (images, voice, documents) downloaded to local dir and served via /media/
  • QR code printed to terminal on first run, session auto-saved for reconnection
  • Auto-reconnects on temporary disconnections

Test plan

  • node --check bridge.js - no syntax errors
  • npm install - dependencies install cleanly
  • Start bridge, verify all 6 HTTP endpoints respond correctly
  • QR code renders in terminal via qrcode-terminal
  • Graceful shutdown on SIGTERM

Closes #31

Add Node.js bridge script that connects to WhatsApp Web using Baileys
(no browser/Chromium dependency) and exposes HTTP endpoints consumed by
the existing Python WhatsApp adapter.

Bridge endpoints:
- GET  /messages     - poll incoming messages (queue + clear)
- POST /send         - send message with optional reply-to
- POST /typing       - typing indicator
- GET  /chat/:id     - chat info (group metadata, DM name)
- GET  /health       - connection status
- GET  /media/:file  - serve downloaded media

Also fixes the Python adapter:
- Remove stdout/stderr piping so QR code is visible to the user
- Replace fixed 5s sleep with health endpoint polling (up to 30s)

Update WhatsApp setup docs in docs/messaging.md with concrete steps.
@teknium1

Copy link
Copy Markdown
Contributor

Sorry I fixed this issue earlier to resolve someone's reported issue, 9fc0ca0

@teknium1 teknium1 closed this Feb 26, 2026
h4x3rotab pushed a commit to Clawdi-AI/hermes-agent that referenced this pull request Apr 10, 2026
…usResearch#36)

* fix(mobile): keep chat scrollable when iOS keyboard opens

* fix: improve startup hang UX with timeout, retry, and error state

---------

Co-authored-by: outsourc-e <eric@outsourc.e>

@OmarB97 OmarB97 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: Critical bug in _clear_low_information_state scoped clearing

BLOCKER: _clear_low_information_state(tool_name) is called but scoped clearing is not implemented

File: agent/tool_guardrails.py
Lines: _clear_low_information_state method and its call in before_call

The new _clear_low_information_state method accepts a tool_name parameter but only implements the None case (clears all low-info state). When called from before_call with tool_name="terminal":

if tool_name == "terminal" and _terminal_probe_family(args) != "filter_probe":
    self._clear_low_information_state(tool_name)
    return ToolGuardrailDecision(...)

The method falls through and does nothing because the else branch for scoped clearing is missing. This means:

  1. Running a broad diagnostic like pwd && ls -la does NOT reset the filter-probe low-info state or clear the tool redirect
  2. Subsequent filter probes will still hit the redirect instead of being allowed through
  3. Test test_terminal_filter_redirect_allows_broad_diagnostic_to_reset will fail on its second assertion because self._tool_redirects["terminal"] is never popped

The fix needs to add scoped clearing that:

  • Removes all _low_information_counts entries with k[0] == tool_name
  • Pops the tool from _tool_redirects (e.g., self._tool_redirects.pop(tool_name, None))

Minor: _as_positive_int changes behavior for max_chars=0

The old code in conversation_loop.py accepted HERMES_STALL_RETRY_MAX_CHARS=0 as a valid int. The new _as_positive_int helper silently upgrades 0 to the default (400). While 0 is unlikely in practice, this is an undocumented behavioral change.

Minor: _stall_retry_config calls load_config() on every access

Each of get_stall_retry_model, get_stall_retry_max_chars, and get_stall_retry_max_per_turn calls _stall_retry_config(agent) independently. In the fallback path (when agent._stall_retry_config is not set), this could parse the config file 3+ times per stall check. Consider caching the loaded config at module level or using a single call.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Setup wizard missing Nous Portal provider option and WhatsApp configuration

3 participants