* feat(whatsapp): subscribe to inbound reaction events
Subscribe to Baileys `messages.reaction` events and surface them as
system events for the agent session. Follows the same pattern used by
Signal and Slack: listen → parse → route → enqueueSystemEvent.
- Add `WebInboundReaction` type and `onReaction` callback to monitor
- Parse emoji, sender JID, target message ID from Baileys event
- Route via `resolveAgentRoute` and enqueue as system event
- Handle self-reaction attribution (reactionKey.fromMe → selfJid)
- Skip reaction removals (empty emoji) and status/broadcast JIDs
- Outer + inner try/catch for resilience against malformed events
- 13 unit tests (monitor-level + system event wiring)
- Changelog entry and docs updates
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* revert changelog entry to avoid merge conflicts
The inbound reaction feature is internal plumbing, not user-facing enough
to warrant a changelog entry that will conflict with active PRs.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat(whatsapp): handle reaction removals and improve sender detection
- Emit reaction events with isRemoval flag instead of skipping them
- Fall back to chatJid for DM sender when reaction.key is missing
- Add chatType and accountId to reaction logs for observability
- Update tests to reflect new removal handling behavior
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(whatsapp): gate reactions by DM/group access controls
Address Codex review - reactions now respect the same access controls
as messages (dmPolicy, allowlists, etc). Self-reactions bypass the
check since they're our own actions, not inbound events.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: remove unused senderName field from reaction type
Baileys reaction events don't include push names, so this field
was dead interface pollution. (Cursor review)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(whatsapp): update reaction docs + suppress pairing for reactions
- Update docs: reaction removals now emit events with isRemoval=true
- Pass no-op sendMessage to access control to prevent pairing messages
being sent when unknown users react (pairing is for messages, not reactions)
(Cursor review)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Nick Sullivan <nick@technick.ai>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Recent changes added recordProviderActivity calls with accountId, but
the type definition and usage didn't include accountId in ActiveWebSendOptions.
This fix adds the optional accountId field and uses optional chaining
when accessing it to handle cases where options is undefined.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>