feat(slack): per-message thread for top-level posts in per-thread sessions#2472
Open
luisfontes wants to merge 1 commit into
Open
feat(slack): per-message thread for top-level posts in per-thread sessions#2472luisfontes wants to merge 1 commit into
luisfontes wants to merge 1 commit into
Conversation
3 tasks
…sions Implement ChannelAdapter.rewriteThreadIdForSession on the Slack adapter. Slack's chat-sdk adapter encodes top-level (un-threaded) messages with an empty thread_ts — for DMs this means every unsolicited message arrives with the same threadId, so a wiring with `session_mode=per-thread` degenerates to a single ever-growing session and outbound replies post as flat channel messages instead of in-thread (postMessage decodes thread_ts from the threadId, and an empty thread_ts means "post at top level"). The rewriter mints a per-message threadId by re-encoding the channel with the message's own ts as thread_ts. Each new top-level post becomes its own thread/session AND the reply posts in-thread because Slack auto-creates the thread on the first reply. In-thread events already carry a real thread_ts (decoded.threadTs is non-empty) so the rewrite no-ops. The router only consults this hook when the wiring's effective session_mode is per-thread (see ChannelAdapter.rewriteThreadIdForSession docs upstream of this branch). In shared / agent-shared mode the threadId doesn't gate the session, so the rewriter wouldn't help and isn't called. Note for review: depends on the host-side hook landing in main first (adapter interface + router integration). Once main is merged forward into channels, this PR rebases trivially. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
9169797 to
35a04d3
Compare
This was referenced May 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TL;DR
Before: a wiring with
session_mode=per-threadon a Slack DM collapses every top-level DM into one ever-growing session, and the bot's replies post as flat top-level messages.After: each top-level DM becomes its own session and the bot's reply posts in-thread.
(No change in
session_mode=shared. Depends on #2471.)Summary
Implements
ChannelAdapter.rewriteThreadIdForSessionon the Slack adapter. When a wiring is insession_mode=per-thread, top-level Slack posts (DMs and channel posts without a parent thread) each get their own thread/session instead of all collapsing under the same threadId. The reply posts in-thread.Why
Slack's
@chat-adapter/slackencodes top-level (un-threaded) messages withthread_ts="". For DMs that means every unsolicited message arrives with the same threadId — so a wiring withsession_mode=per-threadon a DM degenerates to a single ever-growing session, and outbound replies post as flat channel messages (becausepostMessagereadsthread_tsfrom the threadId and empty means "post at top level").The rewriter re-encodes the threadId with the message's own
tsasthread_ts. Each top-level post becomes its own thread/session AND Slack auto-creates the thread on first reply. In-thread events already carry a realthread_ts, sodecoded.threadTsis non-empty and the rewrite no-ops.Why no toggle
The router only consults this hook when the wiring's effective
session_modeisper-thread(see #2471 againstmain).shared/agent-sharedusers see no change. Operators who explicitly opt into per-thread DMs get the correct behavior without any new config flag.Depends on
main).channelsnext mergesmain, this PR rebases trivially. Verified locally by combining both patches: build + channel tests green.Files
src/channels/slack.ts— attachesbridge.rewriteThreadIdForSession.Test plan
session_mode=per-threadDM wiring: top-level DM sends create distinct sessions; bot replies post in-thread.session_mode=sharedDM wiring: no change from today's behavior (hook not consulted).🤖 Generated with Claude Code