Skip to content

[pull] main from rmyndharis:main - #116

Merged
pull[bot] merged 18 commits into
m7fz7:mainfrom
rmyndharis:main
Aug 20, 2026
Merged

[pull] main from rmyndharis:main#116
pull[bot] merged 18 commits into
m7fz7:mainfrom
rmyndharis:main

Conversation

@pull

@pull pull Bot commented Aug 20, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

The chat-scope check on a read receipt folded both sides through
toEngineJid, which reduces @c.us and @s.whatsapp.net to one spelling but
returns @lid unchanged. Baileys stores a DM key under the peer's lid once
WhatsApp addresses the chat that way, so every stored key for such a chat
failed the comparison. The receipt then fell back to a synthesised key
that lost the message's real fromMe and participant.

Compare in the neutral dialect instead, which resolves a lid to its phone
user-part through the session's lid mapping. An unmapped lid still cannot
match, so a key belonging to another chat is rejected exactly as before.
A before-hook that stops a dispatch returned the same null as a genuine
preflight failure, so the reconciler read a deliberate drop as a loss. It
replayed the suppressed event once per sweep until the attempt budget ran
out, then marked the row terminally failed and pointed the operator at a
delivery-failure row nothing had written. Cancellation is now its own
outcome: terminal like a delivery, but recording nothing.

A failed replay leaves its outbox row pending on purpose, so every later
sweep reached the dead-letter table again. One lost delivery was reported
as up to WEBHOOK_RECONCILE_MAX_ATTEMPTS rows and counted that many times
in webhook_delivery_failures_total, against a table documented as holding
one record per delivery that exhausted its retries. The recorder now skips
a delivery it has already recorded, as the inbound reconciler does.

That lookup runs on every terminal failure against a table that is never
pruned, so it gets a composite index on (webhookId, idempotencyKey).
The note told operators to move the whole `<old root>/plugins` tree when
changing the knob. On the default layout that path is PLUGINS_DIR itself,
so following the instruction takes the installed plugin packages away from
the loader, which the same paragraph says to leave in place.

Name the files that actually hold plugin state instead: `registry.json`
and each plugin's `<plugin id>/key-*.json`, copied into the new root.
fix(engine): match receipt keys across the lid dialect
fix(webhook): correct outcome accounting on the replay path
…note

docs(plugins): correct the PLUGIN_STATE_DIR migration note
A caller could tag participants on send-text and the media sends, but the
same array was rejected with "property mentions should not exist" on the
routes that carry text just as plainly: reply, edit, send-template and the
bulk items. The engines can carry the tags on all of them, so the field was
missing from the DTOs rather than from the engines.

- replyToMessage and editMessage take an optional mentions list, forwarded
  by both adapters. Baileys spreads the existing withMentions helper into
  the content; whatsapp-web.js passes the options bag its library already
  accepts, and omits it entirely when no tags were asked for so an untagged
  send keeps its previous call shape.
- send-template dispatches through sendText, so it also gained linkPreview.
  quotedMessageId stays out: docs/06 publishes this route as one that
  rejects it.
- Every bulk item type forwards its own list, audio included. Audio carries
  no caption, but a mention still tags through contextInfo, which is why
  the single-send audio route accepts it.
- An edit REPLACES the message content, so tags are re-applied rather than
  preserved: a rewritten body loses the tags unless the list is sent again.
- The mentions caps and the 4096 text cap move to shared constants; the
  effective limits are unchanged.

Verified: adapter, service and DTO tests per route, each with a negative
control; the DTO cases run through the real production validation pipe, so
they exercise the whitelist that produced the 400. Plugin rewrites of the
list are asserted on reply and edit, because message:sending is a
moderation chokepoint and reading the caller's own body there would send
the unredacted tags.
MessageService.reply declared an inline three-field parameter while the
controller already handed it a ReplyMessageDto carrying a fourth. The body
still reached the sender, because structural typing does not strip excess
properties, but the declaration said less than what flowed through and no
test covered the entry point itself. A non-REST caller that builds the
object against the declared type cannot pass mentions at all.

Type it by ReplyMessageDto, matching every sibling forwarder in the class.
…carrying-route

feat(message): widen mentions to every text-carrying route
An agent could not tag a participant through the MCP surface, and the
failure was silent rather than loud: a tool schema is a plain z.object, so
an undeclared `mentions` argument was stripped before the handler ran and
the send reported success with the message delivered untagged. The same
request over REST tagged correctly.

- The text, image, video, audio, document, template and reply tools take
  the field, forwarding it to the service they already call.
- One shared schema takes both caps and the per-entry WID rule from the
  DTO rather than restating them. A tool handler calls the service
  directly, so the ValidationPipe never runs and this schema is the only
  thing between an agent and the engine. An invalid WID is now refused
  instead of being carried to the adapter.
- Not offered on the sticker tool: both adapters build the sticker content
  without a mention list, so declaring it there would accept the field and
  drop it, which is the defect this change removes everywhere else.
- MessageSendText.text binds to the shared cap constant the file already
  imports, matching MessageReply.text beside it. Same value, no change in
  behaviour.

The REST/MCP cap-parity spec gains a mentions case, so the two sides
cannot drift apart silently.
send-sticker shares SendMediaMessageDto, so the route takes a mentions
array, buildMediaInput forwards it, and docs/06 lists send-sticker among
the media send routes that accept the field. Both adapters then built the
sticker content without a tag list, so the documented capability did
nothing on either engine and the caller was told the send succeeded.

A sticker carries neither text nor caption, but stickerMessage holds a
contextInfo like every other content type, so the tag still reaches the
participant. Same reasoning the audio path already records.

Baileys spreads the existing withMentions helper into the content;
whatsapp-web.js adds the list to the options bag it already passes, and
omits the key when none were given so an untagged sticker send keeps its
previous call shape.
Same silent drop as mentions, on the other field REST already declares.
An agent that supplied a preview had the object stripped by the plain
z.object before the handler ran, and the send went out with whatever
preview the engine chose on its own.

The schema takes all three caps from the DTO rather than restating them,
and enforces the title WhatsApp requires: this path calls the service
directly, so no ValidationPipe ever sees the object.
fix(engine): apply the mentions a sticker send accepts
feat(mcp): accept mentions on the agent tools
The sticker tool was left without mentions because neither adapter built
a tag list for a sticker. That stopped being true one merge earlier: the
adapters gained it, and this tool set was authored against the tree before
that landed, so the exclusion and its three stated reasons shipped stale.

The rationale contradicted itself inside one release: the changelog said
the tool withholds the field because the adapters build no tag list, seven
lines above an entry saying both adapters now build one. A test locked the
drop in place, so it would have resisted the correction.

- MessageSendSticker declares and forwards mentions, like the other seven
  send tools. The route has always accepted the field.
- The exclusion test becomes an inclusion test, and the stale reasons in
  the shared schema docblock and the changelog are gone.
- A comment explaining the sticker mimetype rule sits back above the test
  it explains; a test added last merge had landed between the two.
…-stale-rationale

fix(mcp): tag a sticker send, and drop a rationale time made false
@pull pull Bot locked and limited conversation to collaborators Aug 20, 2026
@pull pull Bot added the ⤵️ pull label Aug 20, 2026
@pull
pull Bot merged commit 09f9c6a into m7fz7:main Aug 20, 2026
14 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant