feat: add slack replyToModeByChatType overrides

This commit is contained in:
Peter Steinberger
2026-01-23 05:24:18 +00:00
parent eebd750781
commit 9bf295da48
7 changed files with 111 additions and 58 deletions

View File

@@ -1,5 +1,6 @@
import type { ClawdbotConfig } from "../config/config.js";
import type { SlackAccountConfig } from "../config/types.js";
import { normalizeChatType } from "../channels/chat-type.js";
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js";
import { resolveSlackAppToken, resolveSlackBotToken } from "./token.js";
@@ -20,6 +21,7 @@ export type ResolvedSlackAccount = {
reactionNotifications?: SlackAccountConfig["reactionNotifications"];
reactionAllowlist?: SlackAccountConfig["reactionAllowlist"];
replyToMode?: SlackAccountConfig["replyToMode"];
replyToModeByChatType?: SlackAccountConfig["replyToModeByChatType"];
actions?: SlackAccountConfig["actions"];
slashCommand?: SlackAccountConfig["slashCommand"];
dm?: SlackAccountConfig["dm"];
@@ -95,6 +97,7 @@ export function resolveSlackAccount(params: {
reactionNotifications: merged.reactionNotifications,
reactionAllowlist: merged.reactionAllowlist,
replyToMode: merged.replyToMode,
replyToModeByChatType: merged.replyToModeByChatType,
actions: merged.actions,
slashCommand: merged.slashCommand,
dm: merged.dm,
@@ -112,7 +115,11 @@ export function resolveSlackReplyToMode(
account: ResolvedSlackAccount,
chatType?: string | null,
): "off" | "first" | "all" {
if (chatType === "direct" && account.dm?.replyToMode !== undefined) {
const normalized = normalizeChatType(chatType ?? undefined);
if (normalized && account.replyToModeByChatType?.[normalized] !== undefined) {
return account.replyToModeByChatType[normalized] ?? "off";
}
if (normalized === "direct" && account.dm?.replyToMode !== undefined) {
return account.dm.replyToMode;
}
return account.replyToMode ?? "off";

View File

@@ -20,7 +20,57 @@ describe("buildSlackThreadingToolContext", () => {
expect(result.replyToMode).toBe("first");
});
it("uses dm.replyToMode for direct messages when configured", () => {
it("uses chat-type replyToMode overrides for direct messages when configured", () => {
const cfg = {
channels: {
slack: {
replyToMode: "off",
replyToModeByChatType: { direct: "all" },
},
},
} as ClawdbotConfig;
const result = buildSlackThreadingToolContext({
cfg,
accountId: null,
context: { ChatType: "direct" },
});
expect(result.replyToMode).toBe("all");
});
it("uses top-level replyToMode for channels when no channel override is set", () => {
const cfg = {
channels: {
slack: {
replyToMode: "off",
replyToModeByChatType: { direct: "all" },
},
},
} as ClawdbotConfig;
const result = buildSlackThreadingToolContext({
cfg,
accountId: null,
context: { ChatType: "channel" },
});
expect(result.replyToMode).toBe("off");
});
it("falls back to top-level when no chat-type override is set", () => {
const cfg = {
channels: {
slack: {
replyToMode: "first",
},
},
} as ClawdbotConfig;
const result = buildSlackThreadingToolContext({
cfg,
accountId: null,
context: { ChatType: "direct" },
});
expect(result.replyToMode).toBe("first");
});
it("uses legacy dm.replyToMode for direct messages when no chat-type override exists", () => {
const cfg = {
channels: {
slack: {
@@ -37,40 +87,6 @@ describe("buildSlackThreadingToolContext", () => {
expect(result.replyToMode).toBe("all");
});
it("uses top-level replyToMode for channels even when dm.replyToMode is set", () => {
const cfg = {
channels: {
slack: {
replyToMode: "off",
dm: { replyToMode: "all" },
},
},
} as ClawdbotConfig;
const result = buildSlackThreadingToolContext({
cfg,
accountId: null,
context: { ChatType: "channel" },
});
expect(result.replyToMode).toBe("off");
});
it("falls back to top-level when dm.replyToMode is not set", () => {
const cfg = {
channels: {
slack: {
replyToMode: "first",
dm: { enabled: true },
},
},
} as ClawdbotConfig;
const result = buildSlackThreadingToolContext({
cfg,
accountId: null,
context: { ChatType: "direct" },
});
expect(result.replyToMode).toBe("first");
});
it("uses all mode when ThreadLabel is present", () => {
const cfg = {
channels: {