fix(slack): enforce replyToMode for auto-thread_ts and inline reply tags (#23839)

* Slack: respect replyToMode for auto-thread_ts and inline reply tags

* Update CHANGELOG.md
This commit is contained in:
Vincent Koc
2026-02-22 14:36:46 -05:00
committed by GitHub
parent 9f7c1686b4
commit 71c2c59c6c
6 changed files with 62 additions and 17 deletions

View File

@@ -103,7 +103,6 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag
incomingThreadTs,
messageTs,
hasRepliedRef,
chatType: prepared.isDirectMessage ? "direct" : "channel",
isThreadReply,
});
@@ -187,6 +186,7 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag
runtime,
textLimit: ctx.textLimit,
replyThreadTs,
replyToMode: ctx.replyToMode,
});
replyPlan.markSent();
};

View File

@@ -16,9 +16,13 @@ export async function deliverReplies(params: {
runtime: RuntimeEnv;
textLimit: number;
replyThreadTs?: string;
replyToMode: "off" | "first" | "all";
}) {
for (const payload of params.replies) {
const threadTs = payload.replyToId ?? params.replyThreadTs;
// Keep reply tags opt-in: when replyToMode is off, explicit reply tags
// must not force threading.
const inlineReplyToId = params.replyToMode === "off" ? undefined : payload.replyToId;
const threadTs = inlineReplyToId ?? params.replyThreadTs;
const mediaList = payload.mediaUrls ?? (payload.mediaUrl ? [payload.mediaUrl] : []);
const text = payload.text ?? "";
if (!text && mediaList.length === 0) {
@@ -88,19 +92,11 @@ function createSlackReplyReferencePlanner(params: {
incomingThreadTs: string | undefined;
messageTs: string | undefined;
hasReplied?: boolean;
chatType?: "direct" | "channel" | "group";
isThreadReply?: boolean;
}) {
// When already inside a Slack thread, stay in it — but for DMs where the
// "thread" was created by the typing indicator (not a real thread reply),
// respect the user's replyToMode setting.
// See: https://github.com/openclaw/openclaw/issues/16868
const effectiveMode =
params.chatType === "direct" && !params.isThreadReply
? params.replyToMode
: params.incomingThreadTs
? "all"
: params.replyToMode;
// Only force threading for real user thread replies. If Slack auto-populates
// thread_ts on top-level messages, preserve the configured reply mode.
const effectiveMode = params.isThreadReply ? "all" : params.replyToMode;
return createReplyReferencePlanner({
replyToMode: effectiveMode,
existingId: params.incomingThreadTs,
@@ -114,7 +110,6 @@ export function createSlackReplyDeliveryPlan(params: {
incomingThreadTs: string | undefined;
messageTs: string | undefined;
hasRepliedRef: { value: boolean };
chatType?: "direct" | "channel" | "group";
isThreadReply?: boolean;
}): SlackReplyDeliveryPlan {
const replyReference = createSlackReplyReferencePlanner({
@@ -122,7 +117,6 @@ export function createSlackReplyDeliveryPlan(params: {
incomingThreadTs: params.incomingThreadTs,
messageTs: params.messageTs,
hasReplied: params.hasRepliedRef.value,
chatType: params.chatType,
isThreadReply: params.isThreadReply,
});
return {