
























@@ -0,0 +1,130 @@
1+import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
2+import {
3+resolveRuntimeConversationBindingRoute,
4+type RuntimeConversationBindingRouteResult,
5+} from "openclaw/plugin-sdk/conversation-runtime";
6+import { resolveAgentRoute } from "openclaw/plugin-sdk/routing";
7+import { resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
8+import { resolveSlackReplyToMode } from "../../account-reply-mode.js";
9+import type { ResolvedSlackAccount } from "../../accounts.js";
10+import { resolveSlackThreadContext } from "../../threading.js";
11+import type { SlackMessageEvent } from "../../types.js";
12+13+export type SlackRoutingContextDeps = {
14+cfg: OpenClawConfig;
15+teamId: string;
16+threadInheritParent: boolean;
17+threadHistoryScope: "thread" | "channel";
18+};
19+20+export type SlackRoutingContext = {
21+route: ReturnType<typeof resolveAgentRoute>;
22+runtimeBinding: RuntimeConversationBindingRouteResult["bindingRecord"];
23+chatType: "direct" | "group" | "channel";
24+replyToMode: ReturnType<typeof resolveSlackReplyToMode>;
25+threadContext: ReturnType<typeof resolveSlackThreadContext>;
26+threadTs: string | undefined;
27+isThreadReply: boolean;
28+threadKeys: ReturnType<typeof resolveThreadSessionKeys>;
29+sessionKey: string;
30+historyKey: string;
31+};
32+33+function resolveSlackBaseConversationId(params: {
34+message: SlackMessageEvent;
35+isDirectMessage: boolean;
36+}): string {
37+return params.isDirectMessage
38+ ? `user:${params.message.user ?? "unknown"}`
39+ : params.message.channel;
40+}
41+42+export function resolveSlackRoutingContext(params: {
43+ctx: SlackRoutingContextDeps;
44+account: ResolvedSlackAccount;
45+message: SlackMessageEvent;
46+isDirectMessage: boolean;
47+isGroupDm: boolean;
48+isRoom: boolean;
49+isRoomish: boolean;
50+}): 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+},
61+});
62+63+const chatType = isDirectMessage ? "direct" : isGroupDm ? "group" : "channel";
64+const replyToMode = resolveSlackReplyToMode(account, chatType);
65+const threadContext = resolveSlackThreadContext({ message, replyToMode });
66+const threadTs = threadContext.incomingThreadTs;
67+const isThreadReply = threadContext.isThreadReply;
68+// Keep true thread replies thread-scoped, but preserve channel-level sessions
69+// for top-level room turns when replyToMode is off.
70+// For DMs, preserve existing auto-thread behavior when replyToMode="all".
71+const autoThreadId =
72+!isThreadReply && replyToMode === "all" && threadContext.messageTs
73+ ? threadContext.messageTs
74+ : 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).
80+const roomThreadId = isThreadReply && threadTs ? threadTs : undefined;
81+const canonicalThreadId = isRoomish ? roomThreadId : isThreadReply ? threadTs : autoThreadId;
82+const baseConversationId = resolveSlackBaseConversationId({ message, isDirectMessage });
83+const boundThreadRoute = canonicalThreadId
84+ ? resolveRuntimeConversationBindingRoute({
85+ route,
86+conversation: {
87+channel: "slack",
88+accountId: account.accountId,
89+conversationId: canonicalThreadId,
90+parentConversationId: baseConversationId,
91+},
92+})
93+ : null;
94+const runtimeRoute =
95+boundThreadRoute?.boundSessionKey || boundThreadRoute?.bindingRecord
96+ ? boundThreadRoute
97+ : resolveRuntimeConversationBindingRoute({
98+ route,
99+conversation: {
100+channel: "slack",
101+accountId: account.accountId,
102+conversationId: baseConversationId,
103+},
104+});
105+route = runtimeRoute.route;
106+const threadKeys = runtimeRoute.boundSessionKey
107+ ? { sessionKey: route.sessionKey, parentSessionKey: undefined }
108+ : resolveThreadSessionKeys({
109+baseSessionKey: route.sessionKey,
110+threadId: canonicalThreadId,
111+parentSessionKey:
112+canonicalThreadId && ctx.threadInheritParent ? route.sessionKey : undefined,
113+});
114+const sessionKey = threadKeys.sessionKey;
115+const historyKey =
116+isThreadReply && ctx.threadHistoryScope === "thread" ? sessionKey : message.channel;
117+118+return {
119+ route,
120+runtimeBinding: runtimeRoute.bindingRecord,
121+ chatType,
122+ replyToMode,
123+ threadContext,
124+ threadTs,
125+ isThreadReply,
126+ threadKeys,
127+ sessionKey,
128+ historyKey,
129+};
130+}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。