feat(discord): add react/unreact actions to the discord tool - #67008
feat(discord): add react/unreact actions to the discord tool#67008Kyzcreig wants to merge 1 commit into
Conversation
The discord REST tool exposed pin/unpin/delete_message but no way for an
agent to add or remove an emoji reaction — the only reaction path was the
adapter's automatic 👀/✅/❌ processing markers, unreachable from a tool call.
So an agent could not react to a Discord message at all.
Add react/unreact as core discord actions via pure REST:
PUT/DELETE /channels/{cid}/messages/{mid}/reactions/{emoji}/@me
The emoji is URL-encoded (Unicode percent-encoded; custom emoji as name:id),
so ':' / '#' / multibyte sequences never corrupt the path. unreact removes
only the bot's OWN reaction (documented in the manifest so an agent doesn't
read the idempotent 204 as 'cleared everyone's'). Per-action 403 hints:
react needs ADD_REACTIONS+READ_MESSAGE_HISTORY, unreact only the latter.
_discord_request raises DiscordAPIError on every non-2xx, so the success
result is emitted only on a genuine 204 — a 404/400/429 surfaces an error,
never a fabricated success. react/unreact inherit the existing
discord.server_actions allowlist exactly (available by default, same as
pin/delete).
Tests: tests/tools/test_discord_react.py (encoding incl. custom + '#',
PUT/DELETE, missing-emoji, non-403 no-fake-success, 403 hint, allowlist
gate, default-unset availability, schema/manifest). Update two core/admin
schema-split tests to derive from _CORE_ACTION_NAMES instead of a hardcoded
literal (behavior-contract, not change-detector). Verified live: added +
removed + re-added ✅ on a real Discord message via the runtime handler.
Co-authored-by: Kyzcreig <9063726+Kyzcreig@users.noreply.github.com>
Related to #29026: this adds agent-initiated Discord REST reactions through the Discord tool; it is distinct from inbound reaction-event routing. |
|
Thanks for the focused Discord-tool addition. The premise is verified on current main: The branch is behind current main, but the current-main blobs for both modified existing files still match the PR base, so this appears mechanically salvageable. Automated hermes-sweeper review. |
|
Thanks for the review, and for verifying the premise against current main rather than taking the PR description's word for it. On the branch being behind main: happy to rebase onto current main whenever that's useful — just say the word and I'll push it. I've deliberately left it alone so far to avoid churning the diff while it's queued for review, but if a fresh merge base makes it easier to assess I'll do it immediately. No other changes pending on my side. Ping me if anything in the REST implementation or the error/permission test coverage wants reworking. |
|
Cross-linking the convergence work: #89405 has been updated to incorporate the Discord reaction goals from this PR while keeping the final surface plugin-owned rather than exposing direct core Discord Attribution for this contribution is included in #89405: #67008 by @Kyzcreig. |
|
Thanks @eyeonall — both for folding this in and for carrying the attribution over to #89405; appreciated.
I read #89405 and agree this is the better shape for the repo: the outbound goals here (bot-applied reactions, REST v10 request building, error/permission handling) are covered by the manifest + delivery helpers, and plugin-owned semantics fits the "conservative at the waist" bar better than two new core tool actions ever did. One scope note for the record, not a blocker: #89405 intentionally doesn't cover the ad-hoc case this PR exposed — a model/agent deciding at runtime to react to an arbitrary inbound message (the manifest path decorates the bot's own outbound sends; Ceding right-of-way: #89405 supersedes this PR. I'll close this one when it lands (and happy to close sooner if a maintainer prefers). No further changes coming on this branch. |
What
The
discordREST tool exposedpin_message/unpin_message/delete_messagebut no way for an agent to add or remove an emoji reaction. The only reaction path was the Discord adapter's automatic 👀/✅/❌ processing markers, which are unreachable from a tool call — so an agent could not react to a Discord message at all. (send_message action="react"only supports adapters exposing a publicadd_reaction(chat_id, emoji, message_id)coroutine — Signal/iMessage — not Discord.)Fix
Add
react/unreactas corediscordactions via pure REST:urllib.parse.quote(emoji, safe="")) — Unicode percent-encoded, custom emoji asname:id→name%3Aid,#→%23— so:/#/ multibyte sequences never corrupt the path.unreactremoves only the bot's own reaction — stated in the action manifest so an agent doesn't read the idempotent 204 as "cleared everyone's reaction."reactneedsADD_REACTIONS+READ_MESSAGE_HISTORY;unreactneeds onlyREAD_MESSAGE_HISTORY(removing an own reaction doesn't needADD_REACTIONS), so operators aren't told to over-grant._discord_requestraisesDiscordAPIErroron every non-2xx, so the success result is emitted only on a genuine 204 — a 404/400/429 surfaces an error, never a fabricated success.discord.server_actionsallowlist exactly (available by default when unset, same aspin_message/delete_message; gated when the key is a list).Tests
tests/tools/test_discord_react.py: encoding (Unicode ✅, customname:id, reserved#), PUT vs DELETE, missing-emoji error, non-403 (404) does not fabricate success, 403 actionable hint, allowlist gate, default-unset availability, schema/manifest wiring. Updated two core/admin schema-split tests to derive from_CORE_ACTION_NAMESinstead of a hardcoded literal (behavior-contract, not change-detector). 109 tests pass.Verified live: added + removed + re-added ✅ on a real Discord message via the runtime handler.