* 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>
* feat: audit fixes and documentation improvements
- Refactored model selection to drop legacy fallback and add warning
- Improved heartbeat content validation
- Added Skill Creation guide
- Updated CONTRIBUTING.md with roadmap
* style: fix formatting in model-selection.ts
* style: fix formatting and improve model selection logic with tests
* feat(discord): add exec approval forwarding to DMs
Add support for forwarding exec approval requests to Discord DMs,
allowing users to approve/deny command execution via interactive buttons.
Features:
- New DiscordExecApprovalHandler that connects to gateway and listens
for exec.approval.requested/resolved events
- Sends DMs with embeds showing command details and 3 buttons:
Allow once, Always allow, Deny
- Configurable via channels.discord.execApprovals with:
- enabled: boolean
- approvers: Discord user IDs to notify
- agentFilter: only forward for specific agents
- sessionFilter: only forward for matching session patterns
- Updates message embed when approval is resolved or expires
Also fixes exec completion routing: when async exec completes after
approval, the heartbeat now uses a specialized prompt to ensure the
model relays the result to the user instead of responding HEARTBEAT_OK.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: generic exec approvals forwarding (#1621) (thanks @czekaj)
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>