feat(slack): opt-in Block Kit rendering for agent messages

Add platforms.slack.extra.rich_blocks (default off). When enabled, the
final agent message is sent as Slack Block Kit blocks — section headers,
dividers, and true nested lists via rich_text — instead of flat mrkdwn.

- New plugins/platforms/slack/block_kit.py: pure markdown->blocks renderer
  (headers, dividers, nested ordered/bullet lists, blockquotes, fenced code;
  pipe-tables as aligned monospace since Block Kit has no robust table block).
  Enforces Slack's 50-block / 3000-char section limits and returns None to
  fall back to plain text on empty/oversized/unexpected input. Never raises.
- adapter.send(): render blocks on the single-chunk primary message; a
  text= fallback is ALWAYS sent alongside (notifications/accessibility).
- adapter.edit_message(): blocks only on finalize=True, so intermediate
  streaming edits stay plain mrkdwn (no per-flush block re-derivation).
- Docs (EN + zh-Hans) + config example. Send-side only: no app reinstall.

Tests: pure-renderer unit suite + adapter integration suite (blocks present
when on, plain text when off, text fallback always set, finalize gating,
multi-chunk fallback). Prove-failed against a stubbed renderer.
This commit is contained in:
Ben 2026-07-01 14:57:32 +10:00
parent 44ddc552f5
commit cbc27c8ef8
6 changed files with 707 additions and 5 deletions

View file

@ -343,6 +343,14 @@ platforms:
# (Slack's "Also send to channel" feature).
# Only the first chunk of the first reply is broadcast.
reply_broadcast: false
# Render agent messages as Slack Block Kit blocks (default: false).
# When true, the final agent message is sent with structured blocks —
# section headers, dividers, and true nested lists (via rich_text) —
# instead of flat mrkdwn text. A plain-text fallback is always sent
# alongside for notifications/accessibility. Markdown tables are
# rendered as aligned monospace (Block Kit has no native table block).
rich_blocks: false
```
| Key | Default | Description |
@ -350,6 +358,7 @@ platforms:
| `platforms.slack.reply_to_mode` | `"first"` | Threading mode for multi-part messages: `"off"`, `"first"`, or `"all"` |
| `platforms.slack.extra.reply_in_thread` | `true` | When `false`, channel messages get direct replies instead of threads. Messages inside existing threads still reply in-thread. |
| `platforms.slack.extra.reply_broadcast` | `false` | When `true`, thread replies are also posted to the main channel. Only the first chunk is broadcast. |
| `platforms.slack.extra.rich_blocks` | `false` | When `true`, agent messages are rendered as [Block Kit](https://docs.slack.dev/block-kit/) blocks (headers, dividers, true nested lists). A plain-text fallback is always sent. Tables render as aligned monospace. No app reinstall required — it's a send-side change only. |
### Session Isolation