fix(slack): thread channel ID through inbound context for reactions (#34831)

Slack reaction/thread context routing fixes via canonical synthesis of #34831.

Co-authored-by: Tak <tak@users.noreply.github.com>
This commit is contained in:
dunamismax
2026-03-05 21:47:31 -05:00
committed by GitHub
parent 909f26a26b
commit 1efa7a88c4
7 changed files with 45 additions and 3 deletions

View File

@@ -58,6 +58,7 @@ export function buildThreadingToolContext(params: {
ReplyToId: sessionCtx.ReplyToId,
ThreadLabel: sessionCtx.ThreadLabel,
MessageThreadId: sessionCtx.MessageThreadId,
NativeChannelId: sessionCtx.NativeChannelId,
},
hasRepliedRef,
}) ?? {};

View File

@@ -142,6 +142,8 @@ export type MsgContext = {
GatewayClientScopes?: string[];
/** Thread identifier (Telegram topic id or Matrix thread event id). */
MessageThreadId?: string | number;
/** Platform-native channel/conversation id (e.g. Slack DM channel "D…" id). */
NativeChannelId?: string;
/** Telegram forum supergroup marker. */
IsForum?: boolean;
/** Warning: DM has topics enabled but this message is not in a topic. */

View File

@@ -257,6 +257,8 @@ export type ChannelThreadingContext = {
ReplyToIdFull?: string;
ThreadLabel?: string;
MessageThreadId?: string | number;
/** Platform-native channel/conversation id (e.g. Slack DM channel "D…" id). */
NativeChannelId?: string;
};
export type ChannelThreadingToolContext = {

View File

@@ -727,6 +727,7 @@ export async function prepareSlackMessage(params: {
CommandAuthorized: commandAuthorized,
OriginatingChannel: "slack" as const,
OriginatingTo: slackTo,
NativeChannelId: message.channel,
}) satisfies FinalizedMsgContext;
const pinnedMainDmOwner = isDirectMessage
? resolvePinnedMainDmOwnerFromAllowlist({

View File

@@ -144,4 +144,35 @@ describe("buildSlackThreadingToolContext", () => {
});
expect(result.replyToMode).toBe("off");
});
it("extracts currentChannelId from channel: prefixed To", () => {
const result = buildSlackThreadingToolContext({
cfg: emptyCfg,
accountId: null,
context: { ChatType: "channel", To: "channel:C1234ABC" },
});
expect(result.currentChannelId).toBe("C1234ABC");
});
it("uses NativeChannelId for DM when To is user-prefixed", () => {
const result = buildSlackThreadingToolContext({
cfg: emptyCfg,
accountId: null,
context: {
ChatType: "direct",
To: "user:U8SUVSVGS",
NativeChannelId: "D8SRXRDNF",
},
});
expect(result.currentChannelId).toBe("D8SRXRDNF");
});
it("returns undefined currentChannelId when neither channel: To nor NativeChannelId is set", () => {
const result = buildSlackThreadingToolContext({
cfg: emptyCfg,
accountId: null,
context: { ChatType: "direct", To: "user:U8SUVSVGS" },
});
expect(result.currentChannelId).toBeUndefined();
});
});

View File

@@ -19,10 +19,14 @@ export function buildSlackThreadingToolContext(params: {
const hasExplicitThreadTarget = params.context.MessageThreadId != null;
const effectiveReplyToMode = hasExplicitThreadTarget ? "all" : configuredReplyToMode;
const threadId = params.context.MessageThreadId ?? params.context.ReplyToId;
// For channel messages, To is "channel:C…" — extract the bare ID.
// For DMs, To is "user:U…" which can't be used for reactions; fall back
// to NativeChannelId (the raw Slack channel id, e.g. "D…").
const currentChannelId = params.context.To?.startsWith("channel:")
? params.context.To.slice("channel:".length)
: params.context.NativeChannelId?.trim() || undefined;
return {
currentChannelId: params.context.To?.startsWith("channel:")
? params.context.To.slice("channel:".length)
: undefined,
currentChannelId,
currentThreadTs: threadId != null ? String(threadId) : undefined,
replyToMode: effectiveReplyToMode,
hasRepliedRef: params.hasRepliedRef,