#11608: feat(slack): native streaming, Block Kit blocks, tool-aware status
channel: slack
docker
agents
size: L
Cluster:
Block Streaming Enhancements
## Summary
Adds three Slack capabilities to the existing adapter:
- **Native streaming** — Uses Slack's `chat.startStream` / `appendStream` / `stopStream` API to deliver replies as a single live-updating message instead of separate messages per block. Gated by `channels.slack.streaming: true` config.
- **Block Kit support** — Passes optional `blocks` alongside `text` in `chat.postMessage`, enabling richer message formatting. Blocks are read from `ReplyPayload.channelData.slackBlocks`.
- **Tool-aware status indicators** — Extracts tool names from tool result text to show richer thread status messages ("Using vault read..." instead of generic "is typing...").
All changes are backward-compatible and opt-in. Non-streaming channels and existing Slack setups are unaffected.
## Changes
| File | Change |
|---|---|
| `src/slack/send.ts` | Accept optional `blocks: KnownBlock[]` in send options, pass to `chat.postMessage` |
| `src/slack/stream.ts` | **New** — wraps Slack streaming API with `chatStream()` helper and raw `apiCall` fallback |
| `src/slack/monitor/replies.ts` | Extract `slackBlocks` from `channelData` and pass to `sendMessageSlack` |
| `src/slack/monitor/message-handler/dispatch.ts` | Wire streaming into deliver callback; extract tool status hints; graceful fallback on stream failure |
| `src/config/types.slack.ts` | Add `streaming?: boolean` to `SlackAccountConfig` |
| `src/config/zod-schema.providers-core.ts` | Add `streaming` to `SlackAccountSchema` |
## Design decisions
- **Streaming is separate from `blockStreaming`**: `blockStreaming` controls OpenClaw's internal chunking; `streaming` controls whether Slack's native streaming API is used for delivery. Both should be enabled for the best experience.
- **Tool results bypass streaming**: Tool result messages are short status updates that should appear as separate messages, not appended to the streaming response.
- **Graceful fallback**: If `chat.startStream` fails (e.g., missing scopes, API unavailable), the entire response falls back to normal `postMessage` delivery. Mid-stream failures also fall back cleanly.
- **`chatStream()` helper preferred**: Uses the SDK helper when available (>= 7.11.0), falls back to raw `apiCall` for edge cases.
## Configuration
```json
{
"channels": {
"slack": {
"blockStreaming": true,
"streaming": true
}
}
}
```
## Test plan
- [ ] `pnpm build && pnpm check` passes (verified in Docker build)
- [ ] Non-streaming Slack delivery unchanged when `streaming` is unset/false
- [ ] With `streaming: true`: responses appear as live-updating single message
- [ ] Stream failure mid-response falls back to normal delivery
- [ ] Tool results show as separate messages with tool-aware status
- [ ] Block Kit blocks render when `channelData.slackBlocks` is provided
- [ ] Non-Slack channels (WhatsApp, Telegram, etc.) completely unaffected
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
This PR extends the Slack adapter with (1) native message streaming via Slack’s `chat.startStream`/`appendStream`/`stopStream`, (2) optional Block Kit `blocks` support on `chat.postMessage`, and (3) more descriptive Slack thread status updates by extracting tool names from tool-result text. Config and Zod schemas are updated to add a `channels.slack.streaming` boolean.
Overall the changes integrate into the existing reply-dispatch flow by swapping the Slack deliver callback when streaming is enabled, while leaving other channels untouched. The main issues to address before merge are around Slack API correctness in the new streaming path and ensuring Block Kit blocks are only sent once per message send.
<h3>Confidence Score: 3/5</h3>
- This PR is close, but has a few Slack API correctness issues that should be fixed before merging.
- Core approach looks reasonable and scoped to Slack, but the streaming implementation has at least one definite DM bug (using a user id where a channel/conversation id is required) and the raw streaming fallback appears to call `chat.appendStream` with the wrong parameter name. Block Kit blocks can also be resent on later chunks due to string-equality gating.
- src/slack/monitor/message-handler/dispatch.ts, src/slack/stream.ts, src/slack/send.ts
<!-- greptile_other_comments_section -->
<sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub>
<!-- /greptile_comment -->
Most Similar PRs
#20623: fix(slack): duplicate replies and missing streaming recipient params
by rahulsub-be · 2026-02-19
83.5%
#21218: fix(slack): pass recipient_team_id and recipient_user_id to chat stre…
by apham0001 · 2026-02-19
80.7%
#15864: feat: add deliverOnlyToolMessages config for clean messaging channe...
by gandalf-the-engineer · 2026-02-14
80.4%
#22476: fix(slack): add streamMode to Zod schema and support "off" mode
by ms-ponyo · 2026-02-21
79.3%
#22096: fix(slack): traverse .original for Slack SDK errors; pass recipient...
by maiclaw · 2026-02-20
78.2%
#19083: Slack: preserve per-thread context and consistent thread replies
by jkimbo · 2026-02-17
77.9%
#21754: slack: pass inbound team_id into stream routing and startStream
by AIflow-Labs · 2026-02-20
77.2%
#17316: fix: ack reaction not removed when block streaming is enabled (Tele...
by czmathew · 2026-02-15
77.1%
#9181: feat(slack): add per-channel thread config override
by halbotley · 2026-02-05
77.0%
#20479: fix(slack): keep replies flowing for oversized file uploads
by olyashok · 2026-02-19
77.0%