@@ -20,6 +20,7 @@ export type SlackRoutingContextDeps = {
2020export type SlackRoutingContext = {
2121 route : ReturnType < typeof resolveAgentRoute > ;
2222 runtimeBinding : RuntimeConversationBindingRouteResult [ "bindingRecord" ] ;
23+ runtimeBoundSessionKey : string | undefined ;
2324 chatType : "direct" | "group" | "channel" ;
2425 replyToMode : ReturnType < typeof resolveSlackReplyToMode > ;
2526 threadContext : ReturnType < typeof resolveSlackThreadContext > ;
@@ -39,6 +40,25 @@ function resolveSlackBaseConversationId(params: {
3940 : params . message . channel ;
4041}
4142
43+ function resolveSlackInitialAgentRoute ( params : {
44+ ctx : SlackRoutingContextDeps ;
45+ account : ResolvedSlackAccount ;
46+ message : SlackMessageEvent ;
47+ isDirectMessage : boolean ;
48+ isRoom : boolean ;
49+ } ) {
50+ return resolveAgentRoute ( {
51+ cfg : params . ctx . cfg ,
52+ channel : "slack" ,
53+ accountId : params . account . accountId ,
54+ teamId : params . ctx . teamId || undefined ,
55+ peer : {
56+ kind : params . isDirectMessage ? "direct" : params . isRoom ? "channel" : "group" ,
57+ id : params . isDirectMessage ? ( params . message . user ?? "unknown" ) : params . message . channel ,
58+ } ,
59+ } ) ;
60+ }
61+
4262export function resolveSlackRoutingContext ( params : {
4363 ctx : SlackRoutingContextDeps ;
4464 account : ResolvedSlackAccount ;
@@ -47,17 +67,24 @@ export function resolveSlackRoutingContext(params: {
4767 isGroupDm : boolean ;
4868 isRoom : boolean ;
4969 isRoomish : boolean ;
70+ seedTopLevelRoomThread ?: boolean ;
5071} ) : SlackRoutingContext {
51- const { ctx, account, message, isDirectMessage, isGroupDm, isRoom, isRoomish } = params ;
52- let route = resolveAgentRoute ( {
53- cfg : ctx . cfg ,
54- channel : "slack" ,
55- accountId : account . accountId ,
56- teamId : ctx . teamId || undefined ,
57- peer : {
58- kind : isDirectMessage ? "direct" : isRoom ? "channel" : "group" ,
59- id : isDirectMessage ? ( message . user ?? "unknown" ) : message . channel ,
60- } ,
72+ const {
73+ ctx,
74+ account,
75+ message,
76+ isDirectMessage,
77+ isGroupDm,
78+ isRoom,
79+ isRoomish,
80+ seedTopLevelRoomThread,
81+ } = params ;
82+ let route = resolveSlackInitialAgentRoute ( {
83+ ctx,
84+ account,
85+ message,
86+ isDirectMessage,
87+ isRoom,
6188 } ) ;
6289
6390 const chatType = isDirectMessage ? "direct" : isGroupDm ? "group" : "channel" ;
@@ -72,21 +99,32 @@ export function resolveSlackRoutingContext(params: {
7299 ! isThreadReply && replyToMode === "all" && threadContext . messageTs
73100 ? threadContext . messageTs
74101 : undefined ;
75- // Only fork channel/group messages into thread-specific sessions when they are
76- // actual thread replies (thread_ts present, different from message ts).
77- // Top-level channel messages must stay on the per-channel session for continuity.
78- // Before this fix, every channel message used its own ts as threadId, creating
79- // isolated sessions per message (regression from #10686).
102+ // Keep ordinary top-level room messages on the per-channel session for
103+ // continuity, but preserve Slack thread identity when the event already has
104+ // one or when an actionable app mention will seed a reply thread.
105+ // This keeps a thread root and its later replies on one parent session
106+ // without returning to the old "every channel message is its own thread"
107+ // behavior (regression from #10686).
108+ const seedCandidateThreadId = threadContext . incomingThreadTs ?? threadContext . messageTs ;
109+ const seededRoomThreadId =
110+ ! isThreadReply &&
111+ isRoom &&
112+ seedTopLevelRoomThread &&
113+ replyToMode !== "off" &&
114+ seedCandidateThreadId
115+ ? seedCandidateThreadId
116+ : undefined ;
80117 const roomThreadId = isThreadReply && threadTs ? threadTs : undefined ;
81118 const canonicalThreadId = isRoomish ? roomThreadId : isThreadReply ? threadTs : autoThreadId ;
119+ const routedThreadId = canonicalThreadId ?? ( isRoomish ? seededRoomThreadId : undefined ) ;
82120 const baseConversationId = resolveSlackBaseConversationId ( { message, isDirectMessage } ) ;
83- const boundThreadRoute = canonicalThreadId
121+ const boundThreadRoute = routedThreadId
84122 ? resolveRuntimeConversationBindingRoute ( {
85123 route,
86124 conversation : {
87125 channel : "slack" ,
88126 accountId : account . accountId ,
89- conversationId : canonicalThreadId ,
127+ conversationId : routedThreadId ,
90128 parentConversationId : baseConversationId ,
91129 } ,
92130 } )
@@ -107,9 +145,8 @@ export function resolveSlackRoutingContext(params: {
107145 ? { sessionKey : route . sessionKey , parentSessionKey : undefined }
108146 : resolveThreadSessionKeys ( {
109147 baseSessionKey : route . sessionKey ,
110- threadId : canonicalThreadId ,
111- parentSessionKey :
112- canonicalThreadId && ctx . threadInheritParent ? route . sessionKey : undefined ,
148+ threadId : routedThreadId ,
149+ parentSessionKey : routedThreadId && ctx . threadInheritParent ? route . sessionKey : undefined ,
113150 } ) ;
114151 const sessionKey = threadKeys . sessionKey ;
115152 const historyKey =
@@ -118,6 +155,7 @@ export function resolveSlackRoutingContext(params: {
118155 return {
119156 route,
120157 runtimeBinding : runtimeRoute . bindingRecord ,
158+ runtimeBoundSessionKey : runtimeRoute . boundSessionKey ,
121159 chatType,
122160 replyToMode,
123161 threadContext,
0 commit comments