Added a native WhatsApp channel implementation.#655
Added a native WhatsApp channel implementation.#655alexhoshina merged 19 commits intosipeed:refactor/channel-systemfrom
Conversation
nikolasdehor
left a comment
There was a problem hiding this comment.
Good initiative adding native WhatsApp support. The whatsmeow-based approach removes the need for an external bridge, which is a significant UX improvement. However, there are several issues that need addressing before this is merge-ready.
Critical issues
-
agentMu serialization bottleneck: The new
agentMu sync.Mutexinloop.goserializes ALL message processing (processMessage, processSystemMessage, ProcessHeartbeat) across ALL channels through a single global mutex. This means if a Telegram message takes 30 seconds to process, every WhatsApp/Discord/Slack message queues behind it. This is a major regression for multi-channel deployments. The original design processes messages concurrently by design. If the goal is to prevent heartbeat/message races, scope the lock to per-session or per-agent rather than global. -
Security: message content logged in base.go: The added
log.Printf("Not Allowed: Received message from %s: %s", senderID, content)logs the full message content of rejected senders. This leaks user message content into logs. At minimum, truncate the content or remove it entirely. -
Security: no message content validation:
handleIncomingpasses raw WhatsApp message content directly to the agent without any sanitization. While the existing channels do the same, WhatsApp messages can contain zero-width characters, RTL overrides, and other Unicode control characters that could confuse the LLM or cause display issues. -
Empty messages forwarded: If both
GetConversation()andExtendedTextMessage.GetText()return empty (e.g., image-only, sticker, location messages), the empty string is still forwarded to the bus and processed by the agent. Add aif content == "" { return }guard.
Architecture concerns
-
log.Printfvslogger.InfoCF: The native channel uses rawlog.Printfwhile the rest of the codebase uses the structuredloggerpackage. This should be consistent. -
No reconnection handling: The whatsmeow client can disconnect (network issues, WhatsApp server restarts). There is no reconnection logic. The
events.Disconnectedevent should be handled to attempt reconnection with backoff. -
No media support: The comment says "for now we only forward text" but WhatsApp is heavily media-oriented. At minimum, document this as a known limitation or file a follow-up issue.
-
Heavy dependency footprint: whatsmeow + modernc.org/sqlite adds ~25 new transitive dependencies (visible in go.sum). For a project called "picoclaw" (ultra-lightweight), this is worth calling out. The whatsmeow approach is valid, but consider making it a build tag (e.g.,
//go:build whatsapp_native) so users who do not need it do not carry the binary size cost.
Minor
- The
agentMuchanges in loop.go are unrelated to WhatsApp and should be a separate PR. Mixing concurrency model changes with a new channel implementation makes both harder to review and revert.
Please address items 1 (global mutex), 2 (content logging), and 4 (empty message) at minimum.
…ith WhatsApp native support.
|
Thank you for the insightful comments. I've addressed most of them. |
nikolasdehor
left a comment
There was a problem hiding this comment.
Thanks for addressing most of the feedback. Checked the new diff:
Fixed:
- Empty message guard is in place (
if content == "" { return }) — good. - Content sanitization via
SanitizeMessageContentis a solid addition, and the test coverage is appreciated. - Structured logging via
logger.InfoCFreplaces rawlog.Printf— good. - Reconnection with backoff in
handleDisconnectlooks solid.
Still outstanding:
-
Global mutex in
loop.go— The comment on line 265 says "It uses the same mutex as processMessage so heartbeat and user messages never run concurrently." But I don't see the actualagentMufield orLock()/Unlock()calls in the diff forloop.go. Either this was addressed differently (viarunAgentLoopserialization?) or the comment is stale. Could you clarify how heartbeat/message concurrency is handled now? The original concern was about a single global mutex serializing ALL channels — if that's been scoped to per-session, that's fine. -
content_previewstill logged at INFO — Bothbase.go(line 572, 80-char truncated) and the WhatsApp handler (line 924, 50-char truncated) log message content previews at INFO level. While truncation helps, INFO-level logs are often shipped to external log aggregators where message content shouldn't appear. Consider moving these to DEBUG level, or making content logging opt-in via config. This was item 2 in the original review (slightly different form — now it's truncated rather than full content, which is an improvement, but still INFO). -
shell_test.gopermission literal changes — The0755→0o755andinterface{}→anychanges inshell_test.goare cosmetic and unrelated to WhatsApp. They should go in a separate cleanup PR to keep this diff focused. (Same feedback as item 9 in original review about mixing concerns.)
Items 1 and 2 are the remaining blockers from the original review. Item 3 is minor but worth splitting out.
Removing extrnaeous comments about mutex in loop.go Made-with: Cursor
|
Item 1: Sorry, it was a lingering comment. I went back to the original mechanism that was implemented in the agent and removed the global mutex Item 2: Move both of those logs to DEBUG |
|
Hey! We are currently refactoring the channel system and already have a refactoring branch. If possible, please base your development on this branch. Thank you. |
|
Rebased using the channel refactor branch |
|
I have changed your PR target to the refactor branch |
alexhoshina
left a comment
There was a problem hiding this comment.
I haven't thoroughly reviewed the code because there are many non-compliant areas in the directory and the scope of changes.
We hope to minimize the scope of changes for each PR. This PR modifies packages including build, agent, utils, etc., which are unrelated to the channel.
There was a problem hiding this comment.
A separate subpackage directory should be created under channels, for example, channels/whatsapp/
There was a problem hiding this comment.
Perhaps the changes here require a new PR, as modifications in a PR should ideally be confined to a single module (package)
There was a problem hiding this comment.
This should be the new PR
There was a problem hiding this comment.
This should be the new PR
8b1e667 to
ba98069
Compare
|
The refactor branch is scheduled to be merged during the daytime of February 28, 2026, Beijing Time (GMT+8), with the exact time yet to be determined. You may wait until after the merge to request a PR review Or complete the review before finishing the merge; I will review your PR as quickly as possible We have provided comprehensive migration documentation for the new channel system. |
|
I've redone the implementation based on the new channel system and cleaned up some of the code as well. |
Perhaps you need to rebase onto the latest upstream, as I have rebased and reordered commits on the upstream. Sorry for the extra work. Rebasing main is a necessary preparation before merging. During the refactoring, I tried my best to avoid modifications related to channels from entering main, but some changes still slipped through. Therefore, I performed a rebase yesterday. |
Apply go fmt to files that had formatting inconsistencies (alignment, indentation) after rebasing onto refactor/channel-system.
Removed conflicting lines and kept 'allow_from' as an empty array.
…ifecycle - Move runCtx/runCancel creation before event handler registration and QR loop so Stop() can cancel at any point during startup - Replace blocking QR event loop in Start() with a background goroutine that selects on runCtx.Done(), preventing Start() from hanging indefinitely when waiting for QR scan - Track all background goroutines (QR handler, reconnect) with sync.WaitGroup; Stop() waits for them to finish before releasing client/container resources - Cancel runCtx on error paths in Start() to avoid leaked contexts Fixes resource leak introduced in sipeed#655.
|
Good to see this progressing through the channel refactor rebase. @alexhoshina mentioned the refactor branch is scheduled to merge during Feb 28 Beijing time — @adityakalro you may want to hold off on further rebases until that lands to avoid churn. One thing to watch: the refactor branch (PR #877, merged Feb 27) significantly restructured the channel abstraction layer. Make sure the WhatsApp-specific connection lifecycle (reconnect on disconnect, QR re-auth flow) is compatible with the new |
|
@adityakalro A native WhatsApp channel is a great addition to PicoClaw. Getting it running on a Pi Zero 2 W is a solid proof that it works well even on constrained hardware. This opens up a lot of use cases for people who prefer WhatsApp over Discord or Telegram. We're building a PicoClaw Dev Group on Discord for contributors to connect and collaborate. If you'd like to join, send an email to |
…ifecycle - Move runCtx/runCancel creation before event handler registration and QR loop so Stop() can cancel at any point during startup - Replace blocking QR event loop in Start() with a background goroutine that selects on runCtx.Done(), preventing Start() from hanging indefinitely when waiting for QR scan - Track all background goroutines (QR handler, reconnect) with sync.WaitGroup; Stop() waits for them to finish before releasing client/container resources - Cancel runCtx on error paths in Start() to avoid leaked contexts Fixes resource leak introduced in sipeed#655.
…ifecycle - Move runCtx/runCancel creation before event handler registration and QR loop so Stop() can cancel at any point during startup - Replace blocking QR event loop in Start() with a background goroutine that selects on runCtx.Done(), preventing Start() from hanging indefinitely when waiting for QR scan - Track all background goroutines (QR handler, reconnect) with sync.WaitGroup; Stop() waits for them to finish before releasing client/container resources - Cancel runCtx on error paths in Start() to avoid leaked contexts Fixes resource leak introduced in sipeed#655.
Added a native WhatsApp channel implementation.
…ifecycle - Move runCtx/runCancel creation before event handler registration and QR loop so Stop() can cancel at any point during startup - Replace blocking QR event loop in Start() with a background goroutine that selects on runCtx.Done(), preventing Start() from hanging indefinitely when waiting for QR scan - Track all background goroutines (QR handler, reconnect) with sync.WaitGroup; Stop() waits for them to finish before releasing client/container resources - Cancel runCtx on error paths in Start() to avoid leaked contexts Fixes resource leak introduced in sipeed#655.
Added a native WhatsApp channel implementation.
…ifecycle - Move runCtx/runCancel creation before event handler registration and QR loop so Stop() can cancel at any point during startup - Replace blocking QR event loop in Start() with a background goroutine that selects on runCtx.Done(), preventing Start() from hanging indefinitely when waiting for QR scan - Track all background goroutines (QR handler, reconnect) with sync.WaitGroup; Stop() waits for them to finish before releasing client/container resources - Cancel runCtx on error paths in Start() to avoid leaked contexts Fixes resource leak introduced in sipeed#655.
📝 Description
Added a native implementation of Whatsapp channel
🗣️ Type of Change
🤖 AI Code Generation
🔗 Related Issue
📚 Technical Context (Skip for Docs)
🧪 Test Environment
📸 Evidence (Optional)
Click to view Logs/Screenshots
--
2026/02/22 12:27:26 [2026-02-22T20:27:26Z] [INFO] agent: Agent initialized {tools_count=14, skills_total=6, skills_available=6}
2026/02/22 12:27:26 [2026-02-22T20:27:26Z] [INFO] channels: Initializing channel manager
2026/02/22 12:27:26 [2026-02-22T20:27:26Z] [INFO] channels: WhatsApp native channel enabled successfully
2026/02/22 12:27:26 [2026-02-22T20:27:26Z] [INFO] channels: Channel initialization completed {enabled_channels=1}
✓ Channels enabled: [whatsapp]
✓ Gateway started on 0.0.0.0:18790
☑️ Checklist