0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-11 02:59:07 +08:00
discourse/plugins/discourse-ai/spec/lib/modules/ai_bot
Gabriel Grubba 9c09e988b3
FEATURE: Approve AI moderation actions inline in bot chat DMs (#41565)
> Was stacked on #41497, which has since merged. This PR is now rebased
onto `main` and contains only the chat-approval work.

### What

Lets a moderator approve or reject an AI-bot moderation tool action
(`suspend_user` / `silence_user`) **inside a Chat direct message with
the bot**, instead of leaving for the `/review` queue — the chat
counterpart to the base PR's inline PM/topic card.

When the bot queues one of these actions in a DM, it posts a message
with **Approve / Reject** buttons rendered via the Chat plugin's native
interactive **blocks**. Clicking performs the queued action through the
existing `ReviewableAiToolAction` backend (credited to the approving
moderator) and rewrites the message to its resolved state, removing the
buttons.

### How

- `bot.rb` — `enqueue_tool_for_approval` branches on chat context: in
chat it emits a `:chat_approval` signal; in PM/topic it keeps the
existing inline card.
- `playground.rb` — `reply_to_chat_message` posts a bot chat message
carrying the Approve/Reject blocks, in the **same DM thread as the bot's
reply** (AI-bot DM replies are threaded by design). DM channels only.
- `chat_tool_approval.rb` — builds/parses the button `action_id`s,
builds the blocks, and handles the `chat_message_interaction` event:
performs the reviewable and rewrites the message. Runs synchronously so
the buttons clear before the request returns.
- `entry_point.rb` — registers the `:chat_message_interaction` listener.

### Authorization

`Chat::CreateMessageInteraction` only checks channel visibility, so
staff-gating is enforced here: the handler requires
`Reviewable.viewable_by(user)` **and** `Reviewable#perform` re-checks
(`ensure_performed_by_is_a_real_person!` + the approver's guardian).
Non-staff clicks are ignored. A crafted `action_id` can't target another
reviewable — core only matches `action_id`s present in that message's
own blocks.

### Core-chat changes (3 lines)

The blocks system was built for **create-time-only** blocks; nothing had
ever mutated a message's `blocks` after creation. Clearing the buttons
on approve/reject is the first such case, which required:

- `chat-message.js` — make `blocks` a `@tracked` property (so
reassigning it re-renders).
- `chat-channel-subscription-manager.js` +
`chat-channel-thread-subscription-manager.js` — refresh `message.blocks`
in `handleEditMessage` (so the block-clearing edit reaches the client,
in both the channel and thread views).

All are no-ops for the only other block user (category blocks, which are
never edited after creation).

### Testing

`plugins/discourse-ai/spec/lib/ai_bot/chat_tool_approval_spec.rb` —
action-id round-trip, block shape, staff gating, foreign/stale
action-ids, approve/reject, failure surfacing, and an end-to-end run
through the real `Chat::CreateMessageInteraction` service. The existing
`playground_spec.rb` chat-DM tests (threaded conversation + context)
continue to pass. Verified manually in a bot DM.
2026-07-13 17:08:35 -03:00
..
jobs/regular DEV: AI persona to agent migration (#38319) 2026-03-10 15:59:45 +11:00
entry_point_spec.rb DEV: AI persona to agent migration (#38319) 2026-03-10 15:59:45 +11:00
playground_spec.rb FEATURE: Approve AI moderation actions inline in bot chat DMs (#41565) 2026-07-13 17:08:35 -03:00
site_setting_extension_spec.rb DEV: migrate enabled_chat_bot column to a setting (#36707) 2025-12-17 09:26:19 -08:00
stream_reply_custom_tools_session_spec.rb FIX: Agentic agents crash when max_turn_tokens is empty (#40952) 2026-06-16 16:59:58 -03:00