feat(slack): use native chat stream replies - #48066
Conversation
teknium1
left a comment
There was a problem hiding this comment.
Thanks for implementing this against the existing draft-stream transport. The native Slack capability is still absent on current main, so the idea remains useful.
Problems
- The patch targets
gateway/platforms/slack.py, but current main migrated the active adapter toplugins/platforms/slack/adapter.pyin560010547;tests/gateway/test_slack.py:67-71now imports that plugin adapter. This needs a port rather than a direct cherry-pick. - In the PR,
gateway/platforms/slack.py:1281removes native-stream state whenchat_appendStreamfails. The consumer then disables drafts and falls back to normal sends (gateway/stream_consumer.py:1302-1337), sosend()no longer has the state needed to callchat.stopStream. A started native stream can remain open.
Suggested changes
- Port the implementation/tests to
plugins/platforms/slack/adapter.py. - Stop an already-started stream before clearing state on append failure, and add a start → append failure → stop/fallback regression test.
Automated hermes-sweeper review.
| return SendResult(success=True, message_id=state.ts) | ||
| except Exception as e: # pragma: no cover - defensive logging | ||
| logger.error("[Slack] chat.*Stream append error: %s", e, exc_info=True) | ||
| self._native_streams.pop(key, None) |
There was a problem hiding this comment.
If chat_appendStream fails after chat_startStream succeeded, dropping this state prevents the subsequent edit-path fallback from finding and stopping the live native stream. Best-effort chat_stopStream before clearing the entry, and add a regression test for an append failure after a successful start.
…ress (opt-in)
Renders tool progress on Slack as native collapsible task cards
(chat.startStream/appendStream/stopStream with task_update chunks — the
UI Slack's own AI features use) instead of markdown progress bubbles.
Opt-in via display.platforms.slack.tool_progress_native (default false):
flag off is byte-identical to current behavior on every platform.
Card features: one grouped plan-mode card per turn; per-tool entries
with friendly labels, descriptive completion titles and short output
previews; source-link chips on web tools; live numbered cards for
delegate_task children; interleaved 💭 cards carrying the model's full
reasoning (first-sentence TLDR title, complete text in the collapsible
body). Failed calls render 'complete + ✗' rather than Slack's error
status (red triangle reserved for genuine stream breakage); MCP
text-form errors are sniffed so a 403 doesn't render a checkmark.
Empirically measured Slack API behavior this encodes (docs are wrong or
silent on each): recipient_team_id AND recipient_user_id are required
for bot-token streams despite being documented optional;
task_update.title/status REPLACE per update while details APPENDS
across updates (all details sends are therefore deltas); a streamed
message has an absolute ~306s lifetime regardless of append activity —
handled by proactive rollover onto a fresh streamed message with open
tasks replayed ('⤵ continued below'); msg_too_long is per-chunk, not
cumulative (62k cumulative verified clean); leading whitespace can be
stripped at chunk joins while trailing survives.
Delivery correctness: all sends serialized through a FIFO asyncio.Lock;
tool.started drains the reasoning throttle buffer before the tool card
is scheduled (the model completes reasoning before emitting a tool
call, so the buffered tail belongs before the tool entry); reasoning
flushes cut at sentence boundaries with sub-40-char fragments carried
forward; any API failure self-disables and falls back silently to the
markdown path for the rest of the turn.
Files: gateway/slack_task_stream.py (new, self-contained);
gateway/run.py (flag-gated wiring only); gateway/display_config.py
(toggle + 5 tuning knobs via standard display-setting resolution).
Related: NousResearch#17321 NousResearch#29496 (earlier takes on this surface), NousResearch#54522 (same
feature via GatewayEventDispatcher — happy to migrate onto that seam as
a follow-up once it lands and grows reasoning/subagent event types),
NousResearch#48066 (final-answer draft streaming; complementary, different
transport). Depends on the reasoning-delivery fixes in the sibling
fix/gateway-reasoning-delivery PR.
Co-authored-by: Minh Nguyen <menhguin@users.noreply.github.com>
|
Closing as superseded: native chat.startStream reply streaming landed in PR #85476 via the same draft-streaming interface approach you used. Your PR targeted the pre-plugin adapter layout, so #66542's equivalent implementation on the current adapter was cherry-picked instead. Thanks — the finalize-by-stopStream design you documented is exactly what shipped. |
Summary
chat.startStream/chat.appendStream/chat.stopStreamsupport through the existing gateway draft-stream transportTesting
/tmp/hermes-slack-stream-venv/bin/python -m py_compile gateway/platforms/slack.py gateway/run.pySLACK_ALLOWED_CHANNELS= PYTHONPATH=. /tmp/hermes-slack-stream-venv/bin/python -m pytest tests/gateway/test_slack.py -q -o 'addopts='— 200 passedNotes
Slack native streams are used only when
streaming.enabledis on, transport isauto/draft, the SDK exposes the stream methods, and the incoming Slack event provides the user/team metadata Slack requires. Otherwise the existing edit-based streaming path remains the fallback.