feat(slack): native streaming via chat.startStream/appendStream/stopStream - #66542
feat(slack): native streaming via chat.startStream/appendStream/stopStream#66542ashah360 wants to merge 1 commit into
Conversation
…tream Slack's Agents & AI Apps feature ships a native streaming surface that renders a live-typing message instead of the edit-based progressive updates the adapter used until now. The adapter now implements the existing draft-streaming interface: - supports_draft_streaming() opts in whenever the app is connected and native streaming hasn't been detected as unavailable. - send_draft() starts a stream on the first frame (chat.startStream, anchored to the resolved thread_ts, with recipient_team_id/user_id for channel streams) and appends only the delta on subsequent frames (chat.appendStream is append-only). The consumer's trailing cursor glyph is stripped before delta computation. - Unlike Telegram drafts (ephemeral, replaced by a real sendMessage), a Slack stream IS the final message. send() therefore intercepts the turn-final delivery for a chat with an active stream whose streamed text is a prefix of the final content, and seals it via chat.stopStream with the remaining delta instead of posting a duplicate. Rich Block Kit (when enabled) is applied to the sealed message via chat_update, mirroring the finalize path in edit_message. - Feature-gate errors from chat.startStream (not_allowed, missing_scope, unknown_method, ...) are cached on the adapter so subsequent runs skip straight to edit-based streaming with a single warning naming the fix (enable Agents & AI Apps for the app); transient errors only disable drafts for the current run via the consumer's existing send_draft failure handling. - Segment breaks (new draft_id) and disconnect() seal any open stream so chats are never left with a dangling live-typing indicator. No consumer or config changes: streaming.transport auto/draft now lights up native streaming on Slack through the same interface Telegram drafts use, and the edit-based path remains the fallback.
Related to #48066: both implement native Slack reply streaming, but this PR targets the current plugin adapter and includes distinct stream-lifecycle/fallback handling. Maintainers should choose or consolidate the active implementation. |
teknium1
left a comment
There was a problem hiding this comment.
Thanks for building this on the existing draft-stream interface. Native Slack streaming is still absent on current main: gateway/platforms/base.py:2537-2554 defaults the capability off, and gateway/config.py:625-631 identifies Slack as edit-only today.
Problems
- Blocking:
10fc71dc7d6akeys_active_streamsbychat_idand uses channel-only client lookup for stream lifecycle calls. Current Slack sends route by explicit team metadata atplugins/platforms/slack/adapter.py:1390-1397;tests/gateway/test_slack.py:2446-2486covers the sameD_SHAREDchannel across workspaces. Scope stream state and client selection by workspace so one tenant cannot append/seal another tenant's stream. - Blocking: the new
send_draft()exception path drops an existing stream withoutstopStream. The consumer disables drafts after a failed frame (gateway/stream_consumer.py:1302-1339) and falls back to regular delivery (gateway/stream_consumer.py:1674-1701), so an append failure after a successful start can leave a dangling stream and then post a second final message.
Suggested changes
- Preserve workspace routing metadata/client for every stream lifecycle operation and add a shared-channel multi-workspace test.
- Best-effort seal an already-started stream on append failure before fallback, with a regression test.
This is an automated hermes-sweeper review.
| # {"ts": str, "draft_id": int, "sent": str, "started": float} | ||
| # ``sent`` is the raw (pre-mrkdwn) text streamed so far — deltas are | ||
| # computed against it because the streaming API is append-only. | ||
| self._active_streams: Dict[str, Dict[str, Any]] = {} |
There was a problem hiding this comment.
chat_id is not a safe stream key for this adapter's multi-workspace mode. Existing Slack tests use the same D_SHARED channel in two workspaces, and _channel_team is only a mutable fallback; key this state by team plus channel (and preserve that routing identity for stop/update) to prevent one workspace from sealing or updating another workspace's stream.
|
Merged via PR #85476 — your commit was cherry-picked onto current main with your authorship preserved in git log. Thanks for the clean draft-interface implementation; it shipped essentially as-is, with the task-card progress feature layered on top. Closing this PR as landed. |
What
Slack's Agents & AI Apps feature ships a native streaming surface (
chat.startStream/chat.appendStream/chat.stopStream) that renders a live-typing message — much smoother than the edit-based progressive updates the Slack adapter uses today. The bundled slack_sdk already supports these methods; this PR wires them into the adapter through the existing draft-streaming interface (supports_draft_streaming/send_draft), the same one Telegram drafts use. No consumer or config changes.How
supports_draft_streaming()opts in whenever the app is connected and native streaming hasn't been detected as unavailable.send_draft()starts a stream on the first frame (anchored to the resolvedthread_ts, withrecipient_team_id/recipient_user_idfor channel streams) and appends only the delta on subsequent frames (the streaming API is append-only). The consumer's trailing cursor glyph is stripped before delta computation.sendMessage. A Slack stream IS the final message, sosend()intercepts the turn-final delivery for a chat with an active stream whose streamed text is a prefix of the final content, and seals it viachat.stopStreamwith the remaining delta instead of posting a duplicate. Opt-in Block Kit is applied to the sealed message viachat_update, mirroringedit_message's finalize path. Unrelated sends (interim commentary) pass through untouched.chat.startStream(not_allowed,missing_scope,unknown_method, …) are cached on the adapter so subsequent runs skip straight to edit-based streaming, with a single warning naming the fix (enable Agents & AI Apps for the app). Transient errors only disable drafts for the current run via the consumer's existingsend_draftfailure handling.draft_id) anddisconnect()seal any open stream so chats are never left with a dangling live-typing indicator.Fallback behavior
Apps without the AI feature enabled: first streaming attempt fails, adapter caches unsupported, run falls back to edit-based streaming mid-flight (consumer's existing path), all future runs go straight to edits. One warning log total.
Testing
tests/gateway/test_slack_native_streaming.pycovering: stream start/append delta computation, cursor stripping, no-op frames, prefix-mismatch fallback, feature-gate error caching vs transient errors, turn-final sealing (no duplicate post), unrelated-send passthrough, stopStream-failure fallback to a normal post, Block Kit pass after seal, and disconnect cleanup.test_slack.py,test_slack_block_kit_adapter.py, plus all-k streamgateway tests (402 passed).