Compare commits

...

4 Commits

Author SHA1 Message Date
Vincent Koc
01d3b67705 docs(changelog): credit prior Slack recipient-id groundwork for 20988 2026-02-19 14:47:51 -08:00
Vincent Koc
48195a4879 Merge branch 'main' into fix/slack-streaming-team-id 2026-02-19 14:17:19 -08:00
David Szarzynski
b8279e4f27 fix(slack): disable block streaming when native streaming is active
When Slack native streaming (`chat.startStream`/`stopStream`) is enabled,
`disableBlockStreaming` was set to `false`, which activated the app-level
block streaming pipeline. This pipeline intercepted agent output, sent it
via block replies, then dropped the final payloads that would have flowed
through `deliverWithStreaming` to the Slack streaming API — resulting in
zero replies delivered.

Set `disableBlockStreaming: true` when native streaming is active so the
final reply flows through the Slack streaming API path as intended.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 09:28:00 -06:00
David Szarzynski
f0bf3be56d fix(slack): pass recipient_team_id and recipient_user_id to streaming API calls
The Slack Agents & AI Apps streaming API (chat.startStream / chat.stopStream)
requires recipient_team_id and recipient_user_id parameters. Without them,
stopStream fails with 'missing_recipient_team_id' (all contexts) or
'missing_recipient_user_id' (DM contexts), causing streamed messages to
disappear after generation completes.

This passes:
- team_id (from auth.test at provider startup, stored in monitor context)
- user_id (from the incoming message sender, for DM recipient identification)

through to the ChatStreamer via recipient_team_id and recipient_user_id options.

Fixes #19839, #20847, #20299, #19791, #20337

AI-assisted: Written with Claude (Opus 4.6) via OpenClaw. Lightly tested
(unit tests pass, live workspace verification in progress).
2026-02-19 08:46:16 -06:00
3 changed files with 22 additions and 3 deletions

View File

@@ -12,6 +12,7 @@ Docs: https://docs.openclaw.ai
- Docker: pin base images to SHA256 digests in Docker builds to prevent mutable tag drift. (#7734) Thanks @coygeek.
- Provider/HTTP: treat HTTP 503 as failover-eligible for LLM provider errors. (#21086) Thanks @Protocol-zero-0.
- Slack: pass `recipient_team_id` / `recipient_user_id` through Slack native streaming calls so `chat.startStream`/`appendStream`/`stopStream` work reliably across DMs and Slack Connect setups, and disable block streaming when native streaming is active. (#20988) Thanks @Dithilli. Earlier recipient-ID groundwork was contributed in #20377 by @AsserAl1012.
- Discord/Gateway: handle close code 4014 (missing privileged gateway intents) without crashing the gateway. Thanks @thewilloftheshadow.
- Security/Net: strip sensitive headers (`Authorization`, `Proxy-Authorization`, `Cookie`, `Cookie2`) on cross-origin redirects in `fetchWithSsrFGuard` to prevent credential forwarding across origin boundaries. (#20313) Thanks @afurm.

View File

@@ -199,6 +199,8 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag
channel: message.channel,
threadTs: streamThreadTs,
text,
teamId: ctx.teamId,
userId: message.user,
});
replyPlan.markSent();
return;
@@ -354,7 +356,7 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag
skillFilter: prepared.channelConfig?.skills,
hasRepliedRef,
disableBlockStreaming: useStreaming
? false
? true
: typeof account.config.blockStreaming === "boolean"
? !account.config.blockStreaming
: undefined,

View File

@@ -36,6 +36,18 @@ export type StartSlackStreamParams = {
threadTs: string;
/** Optional initial markdown text to include in the stream start. */
text?: string;
/**
* The team ID of the workspace this stream belongs to.
* Required by the Slack API for `chat.startStream` / `chat.stopStream`.
* Obtain from `auth.test` response (`team_id`).
*/
teamId?: string;
/**
* The user ID of the message recipient (required for DM streaming).
* Without this, `chat.stopStream` fails with `missing_recipient_user_id`
* in direct message conversations.
*/
userId?: string;
};
export type AppendSlackStreamParams = {
@@ -64,13 +76,17 @@ export type StopSlackStreamParams = {
export async function startSlackStream(
params: StartSlackStreamParams,
): Promise<SlackStreamSession> {
const { client, channel, threadTs, text } = params;
const { client, channel, threadTs, text, teamId, userId } = params;
logVerbose(`slack-stream: starting stream in ${channel} thread=${threadTs}`);
logVerbose(
`slack-stream: starting stream in ${channel} thread=${threadTs}${teamId ? ` team=${teamId}` : ""}${userId ? ` user=${userId}` : ""}`,
);
const streamer = client.chatStream({
channel,
thread_ts: threadTs,
...(teamId ? { recipient_team_id: teamId } : {}),
...(userId ? { recipient_user_id: userId } : {}),
});
const session: SlackStreamSession = {