mirror of
https://github.com/discourse/discourse.git
synced 2026-08-08 17:53:55 +08:00
> 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. |
||
|---|---|---|
| .. | ||
| configuration | ||
| db/migrate | ||
| evals | ||
| fabricators | ||
| fixtures | ||
| integration | ||
| jobs | ||
| lib | ||
| models | ||
| reports | ||
| requests | ||
| serializers | ||
| services | ||
| shared/inference | ||
| support | ||
| system | ||
| tasks | ||
| plugin_helper.rb | ||
| plugin_spec.rb | ||