Skip to content

feat(whatsapp): bidirectional intelligent routing - #1377

Closed
f-liva wants to merge 13 commits into
librefang:mainfrom
f-liva:fix/937-whatsapp-routing
Closed

f-liva wants to merge 13 commits into
librefang:mainfrom
f-liva:fix/937-whatsapp-routing

Conversation

@f-liva

@f-liva f-liva commented Mar 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements full bidirectional message routing for the WhatsApp gateway, resolving the core issues reported in #937:

  • Stranger ↔ Agent direct conversation: strangers talk directly with the agent instead of being auto-forwarded to the owner. Removed the separate generateSenderAck — the agent's response IS the reply
  • Conversation Tracker: in-memory Map with configurable TTL (default 24h), tracks active stranger sessions with message history and escalation state
  • Owner context injection: [ACTIVE_STRANGER_CONVERSATIONS] block injected into owner messages so the agent knows who's waiting
  • Owner → Stranger relay: [RELAY_TO_STRANGER] tag with JID validation — owner tells agent what to relay, agent reformulates and sends
  • [NOTIFY_OWNER] selective escalation: agent decides when to notify the owner (structured JSON with reason + summary)

Bug fixes included

  • Multi-owner JID routing: replies go to the sender's JID, not always OWNER_JID[0]
  • Rate limiting: per-JID (3 msg/min) for strangers
  • Escalation deduplication: 5-minute cooldown per stranger
  • Prompt injection mitigation: system instructions use structured delimiters, not raw concatenation
  • Non-text media handling: descriptors for photos, videos, voice messages, stickers, locations, contacts, documents (no more silent drop)
  • Audit logging: every relayed message logged with sender, recipient, timestamp, content

Design principle

The gateway is plumbing. The agent is the brain.

No personality, escalation rules, or behavioral logic in the gateway — it provides routing mechanisms ([NOTIFY_OWNER], [RELAY_TO_STRANGER], context injection). All behavior is defined by the agent's identity files (AGENTS.md / SOUL.md).

Configuration

Only one new config field:

[channels.whatsapp]
conversation_ttl_hours = 24  # default

Test plan

  • Stranger sends message → agent responds directly to stranger (not forwarded to owner)
  • Agent includes [NOTIFY_OWNER] → owner receives notification, stranger gets clean response
  • Owner sends message with active conversations → context block injected
  • Owner says "tell Mario X" → agent uses [RELAY_TO_STRANGER] → message delivered to stranger
  • Rate limit: 4th message within 1 minute from same stranger is throttled
  • Media messages (photo, voice, sticker) generate descriptors instead of being dropped
  • Multi-owner: response goes to the JID that sent the message
  • Conversation expires after TTL (24h default)

Closes #937

@houko houko 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.

Solid feature — the three-way routing (stranger→agent, agent→owner notification, owner→stranger relay) is well-structured with good safeguards (rate limiting, dedup, escalation debouncing, audit logging).

A few things to address:

[P1] Hand-rolled TOML parser is fragile
The regex-based TOML parsing doesn't handle comments, escaping, multi-line values, or quoted strings with = inside. Consider using a proper TOML library (npm install toml) instead — the fallback-to-defaults behavior silently masks config mistakes.

[P1] Memory growth at scale
activeConversations, rateLimitMap, and lastEscalationTime are unbounded Maps. The cleanup intervals help, but expired rate limit entries for inactive JIDs won't self-clean. Add max size limits with LRU eviction, or periodic full-scan cleanup.

[P2] loggedOut disconnect reason removed
The change removes DisconnectReason.loggedOut from the reconnection guard. Was this intentional? Without it, a WhatsApp logout will trigger reconnection attempts instead of requiring manual re-login.

[P2] Prompt injection edge cases
The [NOTIFY_OWNER]...[/NOTIFY_OWNER] delimiters work for normal cases, but nested brackets in user messages could confuse the regex. Worth documenting the assumptions.

Minor:

  • Owner number validation (7-15 digits) only warns but doesn't reject — could be surprising if misconfigured
  • No length limits on relay messages
  • Consider what happens if agent response contains both [NOTIFY_OWNER] AND [RELAY_TO_STRANGER] tags simultaneously

Overall the code quality is good and the feature addresses a real need. Happy to approve once P1 items are addressed.

…g#937)

Complete rewrite of WhatsApp gateway message routing:

- Conversation Tracker: in-memory Map with TTL (24h default), tracks
  active stranger sessions with message history, escalation state
- Stranger ↔ Agent direct conversation: strangers talk directly with
  the agent instead of being forwarded to owner. Removed generateSenderAck
- Owner context injection: [ACTIVE_STRANGER_CONVERSATIONS] block injected
  when owner messages agent, with [ESCALATED] markers
- Owner → Stranger relay: [RELAY_TO_STRANGER] tag parsing with JID
  validation against active conversations
- [NOTIFY_OWNER] tag: agent can selectively notify owner with structured
  JSON payload (reason + summary)
- Rate limiting: per-JID (3 msg/min) for strangers with debounce
- Escalation deduplication: 5-minute cooldown per stranger
- Multi-owner fix: replies go to sender's JID, not always OWNER_JID[0]
- Prompt injection mitigation: system instructions use structured
  delimiters instead of raw concatenation
- Non-text media: descriptors for photos, videos, voice messages,
  stickers, locations, contacts, documents (no silent drop)
- Audit logging for all relayed messages

Closes librefang#937
@houko
houko force-pushed the fix/937-whatsapp-routing branch from 212fb62 to 3ab7b35 Compare March 22, 2026 12:10
@houko

houko commented Mar 23, 2026 •

Copy link
Copy Markdown
Contributor

Hey @f-liva — nice work on this feature overall. A couple things before we can merge:

  1. Bug: /conversations endpoint — jsonResponse(res, 200, ...) is missing the req parameter. Other endpoints use jsonResponse(req, res, 200, ...). This will crash at runtime.

  2. lastEscalationTime has no cleanup — rateLimitMap and activeConversations both have periodic sweeps, but lastEscalationTime grows unboundedly. Add a similar cleanup interval.

Please address these and we can merge. Thanks!

…, memory leaks, crash

- Replace hand-rolled regex TOML parser with proper `toml` npm library (P1)
- Add periodic cleanup for lastEscalationTime map to prevent unbounded growth (P1)
- Fix /conversations endpoint crash: missing `req` param in jsonResponse call
- Remove duplicate OWNER_JID declaration (dead code from old routing)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
f-liva added a commit to f-liva/librefang that referenced this pull request Mar 23, 2026
…, memory leaks, crash

- Replace hand-rolled regex TOML parser with proper `toml` npm library (P1)
- Add periodic cleanup for lastEscalationTime map to prevent unbounded growth (P1)
- Fix /conversations endpoint crash: missing `req` param in jsonResponse call
- Remove duplicate OWNER_JID declaration (dead code from old routing)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@f-liva

f-liva commented Mar 23, 2026

Copy link
Copy Markdown
Contributor Author

So, I've made the fixes for this PR, but please don't merge it yet, because over the weekend I rewrote the entire WhatsApp system regarding this issue—and not just that. So, let's hold off on this specific PR for a moment

@f-liva

f-liva commented Mar 23, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by a new PR from a clean branch rebased on current upstream/main. The old branch had a stale base with 3.7MB of unrelated diff noise.

@f-liva f-liva closed this Mar 23, 2026
f-liva added a commit to f-liva/librefang that referenced this pull request Mar 23, 2026
…, memory leaks, crash

- Replace hand-rolled regex TOML parser with proper `toml` npm library (P1)
- Add periodic cleanup for lastEscalationTime map to prevent unbounded growth (P1)
- Fix /conversations endpoint crash: missing `req` param in jsonResponse call
- Remove duplicate OWNER_JID declaration (dead code from old routing)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@f-liva
f-liva deleted the fix/937-whatsapp-routing branch March 26, 2026 20:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/sdk JavaScript and Python SDKs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] WhatsApp gateway unreliable message routing and identity management

2 participants