Skip to content

feat(whatsapp): context-aware emoji reactions via shared resolveReactionMessageId#57226

Merged
mcaxtr merged 1 commit intomainfrom
feat/whatsapp-react-context-defaults
Mar 29, 2026
Merged

feat(whatsapp): context-aware emoji reactions via shared resolveReactionMessageId#57226
mcaxtr merged 1 commit intomainfrom
feat/whatsapp-react-context-defaults

Conversation

@mcaxtr
Copy link
Copy Markdown
Contributor

@mcaxtr mcaxtr commented Mar 29, 2026

Summary

  • Problem: WhatsApp reactions require the model to explicitly provide messageId, unlike Telegram, Signal, and Discord which auto-resolve it from inbound context via the shared resolveReactionMessageId helper.
  • Why it matters: The model can't simply call message({ action: "react", emoji: "👍" }) to react to the current inbound message — it must echo back the message ID from context, which it often fails to do.
  • What changed: Wired resolveReactionMessageId from openclaw/plugin-sdk/channel-actions into the WhatsApp handleAction adapter with safety guards for cross-chat and cross-provider scenarios.
  • What did NOT change (scope boundary): No transport changes (sendReactionWhatsApp, send-api.ts), no schema changes (message-tool.ts), no config changes. The react action was already declared by describeMessageTool.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

Root Cause / Regression History (if applicable)

N/A — This is a new feature, not a fix. WhatsApp reactions were added (551a8d5, Jan 6 2026) with messageId: required and never updated when Telegram got the context fallback (c1b75ab, Feb 23 2026) or when the shared helper was extracted (7066d5e, Mar 2 2026).

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: extensions/whatsapp/src/channel.test.ts
  • Scenario the test should lock in:
    • Explicit messageId wins over context fallback
    • Omitted messageId falls back to toolContext.currentMessageId (WhatsApp source only)
    • Numeric messageId converted to string
    • Missing messageId with no context throws ToolInputError (400, not 500)
    • Cross-chat targeting suppresses fallback (normalized comparison handles whatsapp:+1555 vs +1555)
    • Cross-provider source (e.g. Telegram) suppresses fallback
  • Why this is the smallest reliable guardrail: Tests exercise the adapter layer in channel.ts where the fallback logic lives, mocking the runtime to verify parameter resolution.

User-visible / Behavior Changes

  • The model can now react to the current WhatsApp inbound message by calling message({ action: "react", emoji: "👍" }) without providing messageId — it auto-resolves from context.
  • Explicit messageId still works as before (no breaking change).
  • Requires message tool to be available (e.g. messaging profile or tools.alsoAllow: ["message"]).

Diagram (if applicable)

Before:
model calls message({ action: "react", emoji: "👍" })
  → ToolInputError: messageId required

After:
model calls message({ action: "react", emoji: "👍" })
  → resolveReactionMessageId falls back to toolContext.currentMessageId
  → sendReactionWhatsApp(chatJid, inboundMsgId, "👍")
  → reaction appears on user's message

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No — react action was already declared and gated by channels.whatsapp.actions.reactions
  • Data access scope changed? No
  • Guards: fallback only fires when source is WhatsApp AND target is the same chat (normalized). Cross-chat and cross-provider contexts require explicit messageId.

Repro + Verification

Environment

  • OS: Linux (Ubuntu)
  • Runtime/container: Node 22, pnpm
  • Model/provider: Anthropic claude-sonnet-4-5
  • Integration/channel: WhatsApp (Baileys)
  • Relevant config: tools.profile: "messaging", channels.whatsapp.actions.reactions: true (default)

Steps

  1. Send a WhatsApp message to the bot
  2. In the same conversation, tell the bot: "React to my previous message with 👍"
  3. Bot calls message({ action: "react", emoji: "👍" }) — no messageId needed

Expected

  • Emoji reaction appears on the user's WhatsApp message

Actual

  • Emoji reaction appears on the user's WhatsApp message ✓

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Live-tested on OpenClaw 2026.3.28 (cherry-picked) with WhatsApp DM. Gateway logs confirm tool=message called and reaction delivered. 7 new unit tests in channel.test.ts cover fallback, cross-chat guard, cross-provider guard, and error type preservation.

Human Verification (required)

  • Verified scenarios: DM reaction with omitted messageId (auto-resolved from context), explicit messageId still works
  • Edge cases checked: cross-chat targeting (different to vs currentChannelId with prefix normalization), cross-provider source (Telegram context), missing messageId without context (ToolInputError 400)
  • What you did not verify: Group reactions (require participant field — pre-existing limitation, not introduced by this change)

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? Yes — explicit messageId still works identically
  • Config/env changes? No
  • Migration needed? No

Risks and Mitigations

  • Risk: Group reactions may fail when messageId is auto-resolved but participant is missing (needed for group message keys).
    • Mitigation: Pre-existing limitation — same behavior as before for groups. participant enrichment from context is a follow-up enhancement.

@openclaw-barnacle openclaw-barnacle bot added channel: whatsapp-web Channel integration: whatsapp-web size: S maintainer Maintainer-authored PR labels Mar 29, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 29, 2026

Greptile Summary

This PR wires the shared resolveReactionMessageId helper (from openclaw/plugin-sdk/channel-actions) into the WhatsApp handleAction adapter, allowing the model to react to incoming WhatsApp messages without explicitly supplying messageId. The fallback is guarded by a normalized cross-chat comparison and a provider-origin check (isWhatsAppSource), consistent with how Telegram uses the same helper.

Key behaviors:

  • Explicit messageId in params always wins (no breaking change).
  • Context fallback only fires when toolContext.currentChannelProvider === \"whatsapp\" AND the normalized to/chatJid matches toolContext.currentChannelId.
  • When to is provided but currentChannelId is absent, normalizedCurrent becomes null, forcing isCrossChat = true and suppressing the fallback — a safe default.
  • The error path delegates to readStringParam({ required: true }) to preserve the ToolInputError (400) contract rather than throwing a generic 500.
  • Seven new unit tests cover all branches: explicit id, context fallback, numeric id coercion, no-context error, cross-chat suppression, prefix-matched same-chat, and cross-provider suppression. The previously flagged gaps (missing currentChannelProvider in the cross-chat test, missing currentChannelId in happy-path tests) are resolved in the current revision.

Confidence Score: 5/5

Safe to merge — implementation is correct, all guard conditions are sound, and previous review concerns are resolved.

The fallback logic is well-guarded: cross-chat and cross-provider contexts both suppress the fallback, and the error path correctly produces a 400 ToolInputError rather than a 500. All seven unit tests trace correctly through the implementation (verified by manual trace). The two previously flagged test defects — missing currentChannelProvider in the cross-chat test and missing currentChannelId in the happy-path tests — are both fixed in the current revision. No P0 or P1 findings remain.

No files require special attention.

Important Files Changed

Filename Overview
extensions/whatsapp/src/channel.ts Adds toolContext to handleAction, computes isWhatsAppSource, normalizedTarget, normalizedCurrent, and isCrossChat to gate context fallback, then delegates messageId resolution to resolveReactionMessageId. Logic is correct and safe.
extensions/whatsapp/src/channel.test.ts Seven new unit tests added for messageId resolution; all test paths trace correctly through the channel.ts logic. Previous concerns about missing currentChannelProvider (cross-chat test) and missing currentChannelId (happy-path tests) are resolved in this revision.

Reviews (4): Last reviewed commit: "WhatsApp: use shared resolveReactionMess..." | Re-trigger Greptile

@mcaxtr mcaxtr force-pushed the feat/whatsapp-react-context-defaults branch from 7655593 to 65096c2 Compare March 29, 2026 18:18
@mcaxtr
Copy link
Copy Markdown
Contributor Author

mcaxtr commented Mar 29, 2026

@greptile please re-review this PR.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 65096c22b0

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@mcaxtr mcaxtr force-pushed the feat/whatsapp-react-context-defaults branch from 65096c2 to 9e57bfe Compare March 29, 2026 18:26
@mcaxtr
Copy link
Copy Markdown
Contributor Author

mcaxtr commented Mar 29, 2026

Both review comments addressed:

  • Greptile P1 (cross-chat test wrong branch): Added currentChannelProvider: "whatsapp" so the test exercises the isCrossChat guard, not the !isWhatsAppSource branch.
  • Codex P2 (missing currentChannelId fallback leak): isCrossChat now treats missing/unparseable normalizedCurrent as ineligible when an explicit target is provided, preventing cross-chat message id reuse.

@greptile please re-review this PR.

…ions

Wire the shared resolveReactionMessageId helper into the WhatsApp
channel adapter, matching the pattern already used by Telegram, Signal,
and Discord. The model can now react to the current inbound message
without explicitly providing a messageId.

Safety guards:
- Only falls back to context when the source is WhatsApp
- Suppresses fallback when targeting a different chat (normalized comparison)
- Throws ToolInputError (400) instead of plain Error (500) when messageId
  is missing, preserving gateway error mapping
@mcaxtr mcaxtr force-pushed the feat/whatsapp-react-context-defaults branch from 9e57bfe to e6580d7 Compare March 29, 2026 18:36
@mcaxtr
Copy link
Copy Markdown
Contributor Author

mcaxtr commented Mar 29, 2026

@greptile please re-review this PR.

@mcaxtr mcaxtr merged commit 3464823 into main Mar 29, 2026
10 checks passed
@mcaxtr mcaxtr deleted the feat/whatsapp-react-context-defaults branch March 29, 2026 18:42
@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot bot commented Mar 30, 2026

🤖 We're reviewing this PR with Aisle

We're running a security check on the changes in this PR now. This usually takes a few minutes. ⌛
We'll post the results here as soon as they're ready.

Progress:

  • Analysis
  • Finalization

Latest run failed. Keeping previous successful results. Trace ID: 019d3ae247fe0b3ec655263ed476c868.

Last updated on: 2026-03-30T09:47:26Z

pritchie pushed a commit to pritchie/openclaw that referenced this pull request Mar 30, 2026
…ions (openclaw#57226)

Wire the shared resolveReactionMessageId helper into the WhatsApp
channel adapter, matching the pattern already used by Telegram, Signal,
and Discord. The model can now react to the current inbound message
without explicitly providing a messageId.

Safety guards:
- Only falls back to context when the source is WhatsApp
- Suppresses fallback when targeting a different chat (normalized comparison)
- Throws ToolInputError (400) instead of plain Error (500) when messageId
  is missing, preserving gateway error mapping
alexjiang1 pushed a commit to alexjiang1/openclaw that referenced this pull request Mar 31, 2026
…ions (openclaw#57226)

Wire the shared resolveReactionMessageId helper into the WhatsApp
channel adapter, matching the pattern already used by Telegram, Signal,
and Discord. The model can now react to the current inbound message
without explicitly providing a messageId.

Safety guards:
- Only falls back to context when the source is WhatsApp
- Suppresses fallback when targeting a different chat (normalized comparison)
- Throws ToolInputError (400) instead of plain Error (500) when messageId
  is missing, preserving gateway error mapping
livingghost pushed a commit to livingghost/openclaw that referenced this pull request Mar 31, 2026
…ions (openclaw#57226)

Wire the shared resolveReactionMessageId helper into the WhatsApp
channel adapter, matching the pattern already used by Telegram, Signal,
and Discord. The model can now react to the current inbound message
without explicitly providing a messageId.

Safety guards:
- Only falls back to context when the source is WhatsApp
- Suppresses fallback when targeting a different chat (normalized comparison)
- Throws ToolInputError (400) instead of plain Error (500) when messageId
  is missing, preserving gateway error mapping
pgondhi987 pushed a commit to pgondhi987/openclaw that referenced this pull request Mar 31, 2026
…ions (openclaw#57226)

Wire the shared resolveReactionMessageId helper into the WhatsApp
channel adapter, matching the pattern already used by Telegram, Signal,
and Discord. The model can now react to the current inbound message
without explicitly providing a messageId.

Safety guards:
- Only falls back to context when the source is WhatsApp
- Suppresses fallback when targeting a different chat (normalized comparison)
- Throws ToolInputError (400) instead of plain Error (500) when messageId
  is missing, preserving gateway error mapping
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: whatsapp-web Channel integration: whatsapp-web maintainer Maintainer-authored PR size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant