Compare commits
2 Commits
main
...
feat/slack
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
540d85c316 | ||
|
|
40f0a80208 |
@ -8,6 +8,7 @@ Docs: https://docs.clawd.bot
|
|||||||
- Highlight: Lobster optional plugin tool for typed workflows + approval gates. https://docs.clawd.bot/tools/lobster
|
- Highlight: Lobster optional plugin tool for typed workflows + approval gates. https://docs.clawd.bot/tools/lobster
|
||||||
- Lobster: allow workflow file args via `argsJson` in the plugin tool. https://docs.clawd.bot/tools/lobster
|
- Lobster: allow workflow file args via `argsJson` in the plugin tool. https://docs.clawd.bot/tools/lobster
|
||||||
- Agents: add identity avatar config support and Control UI avatar rendering. (#1329, #1424) Thanks @dlauer.
|
- Agents: add identity avatar config support and Control UI avatar rendering. (#1329, #1424) Thanks @dlauer.
|
||||||
|
- Slack: add chat-type reply threading overrides via `replyToModeByChatType`. (#1442) Thanks @stefangalescu.
|
||||||
- Memory: prevent CLI hangs by deferring vector probes, adding sqlite-vec/embedding timeouts, and showing sync progress early.
|
- Memory: prevent CLI hangs by deferring vector probes, adding sqlite-vec/embedding timeouts, and showing sync progress early.
|
||||||
- BlueBubbles: add `asVoice` support for MP3/CAF voice memos in sendAttachment. (#1477, #1482) Thanks @Nicell.
|
- BlueBubbles: add `asVoice` support for MP3/CAF voice memos in sendAttachment. (#1477, #1482) Thanks @Nicell.
|
||||||
- Docs: add troubleshooting entry for gateway.mode blocking gateway start. https://docs.clawd.bot/gateway/troubleshooting
|
- Docs: add troubleshooting entry for gateway.mode blocking gateway start. https://docs.clawd.bot/gateway/troubleshooting
|
||||||
|
|||||||
@ -304,7 +304,8 @@ Slack uses Socket Mode only (no HTTP webhook server). Provide both tokens:
|
|||||||
"policy": "pairing",
|
"policy": "pairing",
|
||||||
"allowFrom": ["U123", "U456", "*"],
|
"allowFrom": ["U123", "U456", "*"],
|
||||||
"groupEnabled": false,
|
"groupEnabled": false,
|
||||||
"groupChannels": ["G123"]
|
"groupChannels": ["G123"],
|
||||||
|
"replyToMode": "all"
|
||||||
},
|
},
|
||||||
"channels": {
|
"channels": {
|
||||||
"C123": { "allow": true, "requireMention": true },
|
"C123": { "allow": true, "requireMention": true },
|
||||||
@ -361,6 +362,25 @@ By default, Clawdbot replies in the main channel. Use `channels.slack.replyToMod
|
|||||||
|
|
||||||
The mode applies to both auto-replies and agent tool calls (`slack sendMessage`).
|
The mode applies to both auto-replies and agent tool calls (`slack sendMessage`).
|
||||||
|
|
||||||
|
### Per-chat-type threading
|
||||||
|
You can configure different threading behavior per chat type by setting `channels.slack.replyToModeByChatType`:
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
channels: {
|
||||||
|
slack: {
|
||||||
|
replyToMode: "off", // default for channels
|
||||||
|
replyToModeByChatType: {
|
||||||
|
direct: "all", // DMs always thread
|
||||||
|
group: "first" // group DMs/MPIM thread first reply
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
When a chat-type override is set, it takes precedence for that chat type. Otherwise the top-level `replyToMode` is used.
|
||||||
|
|
||||||
### Manual threading tags
|
### Manual threading tags
|
||||||
For fine-grained control, use these tags in agent responses:
|
For fine-grained control, use these tags in agent responses:
|
||||||
- `[[reply_to_current]]` — reply to the triggering message (start/continue thread).
|
- `[[reply_to_current]]` — reply to the triggering message (start/continue thread).
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import {
|
|||||||
readStringParam,
|
readStringParam,
|
||||||
resolveDefaultSlackAccountId,
|
resolveDefaultSlackAccountId,
|
||||||
resolveSlackAccount,
|
resolveSlackAccount,
|
||||||
|
resolveSlackReplyToMode,
|
||||||
resolveSlackGroupRequireMention,
|
resolveSlackGroupRequireMention,
|
||||||
buildSlackThreadingToolContext,
|
buildSlackThreadingToolContext,
|
||||||
setAccountEnabledInConfigSection,
|
setAccountEnabledInConfigSection,
|
||||||
@ -162,8 +163,8 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount> = {
|
|||||||
resolveRequireMention: resolveSlackGroupRequireMention,
|
resolveRequireMention: resolveSlackGroupRequireMention,
|
||||||
},
|
},
|
||||||
threading: {
|
threading: {
|
||||||
resolveReplyToMode: ({ cfg, accountId }) =>
|
resolveReplyToMode: ({ cfg, accountId, chatType }) =>
|
||||||
resolveSlackAccount({ cfg, accountId }).replyToMode ?? "off",
|
resolveSlackReplyToMode(resolveSlackAccount({ cfg, accountId }), chatType),
|
||||||
allowTagsWhenOff: true,
|
allowTagsWhenOff: true,
|
||||||
buildToolContext: (params) => buildSlackThreadingToolContext(params),
|
buildToolContext: (params) => buildSlackThreadingToolContext(params),
|
||||||
},
|
},
|
||||||
|
|||||||
@ -136,6 +136,7 @@ export async function runReplyAgent(params: {
|
|||||||
followupRun.run.config,
|
followupRun.run.config,
|
||||||
replyToChannel,
|
replyToChannel,
|
||||||
sessionCtx.AccountId,
|
sessionCtx.AccountId,
|
||||||
|
sessionCtx.ChatType,
|
||||||
);
|
);
|
||||||
const applyReplyToMode = createReplyToModeFilterForChannel(replyToMode, replyToChannel);
|
const applyReplyToMode = createReplyToModeFilterForChannel(replyToMode, replyToChannel);
|
||||||
const cfg = followupRun.run.config;
|
const cfg = followupRun.run.config;
|
||||||
|
|||||||
@ -204,6 +204,7 @@ export function createFollowupRunner(params: {
|
|||||||
queued.run.config,
|
queued.run.config,
|
||||||
replyToChannel,
|
replyToChannel,
|
||||||
queued.originatingAccountId,
|
queued.originatingAccountId,
|
||||||
|
queued.originatingChatType,
|
||||||
);
|
);
|
||||||
|
|
||||||
const replyTaggedPayloads: ReplyPayload[] = applyReplyThreading({
|
const replyTaggedPayloads: ReplyPayload[] = applyReplyThreading({
|
||||||
|
|||||||
@ -358,6 +358,7 @@ export async function runPreparedReply(
|
|||||||
originatingTo: ctx.OriginatingTo,
|
originatingTo: ctx.OriginatingTo,
|
||||||
originatingAccountId: ctx.AccountId,
|
originatingAccountId: ctx.AccountId,
|
||||||
originatingThreadId: ctx.MessageThreadId,
|
originatingThreadId: ctx.MessageThreadId,
|
||||||
|
originatingChatType: ctx.ChatType,
|
||||||
run: {
|
run: {
|
||||||
agentId,
|
agentId,
|
||||||
agentDir,
|
agentDir,
|
||||||
|
|||||||
@ -39,6 +39,8 @@ export type FollowupRun = {
|
|||||||
originatingAccountId?: string;
|
originatingAccountId?: string;
|
||||||
/** Thread id for reply routing (Telegram topic id or Matrix thread event id). */
|
/** Thread id for reply routing (Telegram topic id or Matrix thread event id). */
|
||||||
originatingThreadId?: string | number;
|
originatingThreadId?: string | number;
|
||||||
|
/** Chat type for context-aware threading (e.g., DM vs channel). */
|
||||||
|
originatingChatType?: string;
|
||||||
run: {
|
run: {
|
||||||
agentId: string;
|
agentId: string;
|
||||||
agentDir: string;
|
agentDir: string;
|
||||||
|
|||||||
@ -31,6 +31,33 @@ describe("resolveReplyToMode", () => {
|
|||||||
expect(resolveReplyToMode(cfg, "discord")).toBe("first");
|
expect(resolveReplyToMode(cfg, "discord")).toBe("first");
|
||||||
expect(resolveReplyToMode(cfg, "slack")).toBe("all");
|
expect(resolveReplyToMode(cfg, "slack")).toBe("all");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("uses chat-type replyToMode overrides for Slack when configured", () => {
|
||||||
|
const cfg = {
|
||||||
|
channels: {
|
||||||
|
slack: {
|
||||||
|
replyToMode: "off",
|
||||||
|
replyToModeByChatType: { direct: "all", group: "first" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as ClawdbotConfig;
|
||||||
|
expect(resolveReplyToMode(cfg, "slack", null, "direct")).toBe("all");
|
||||||
|
expect(resolveReplyToMode(cfg, "slack", null, "group")).toBe("first");
|
||||||
|
expect(resolveReplyToMode(cfg, "slack", null, "channel")).toBe("off");
|
||||||
|
expect(resolveReplyToMode(cfg, "slack", null, undefined)).toBe("off");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to top-level replyToMode when no chat-type override is set", () => {
|
||||||
|
const cfg = {
|
||||||
|
channels: {
|
||||||
|
slack: {
|
||||||
|
replyToMode: "first",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as ClawdbotConfig;
|
||||||
|
expect(resolveReplyToMode(cfg, "slack", null, "direct")).toBe("first");
|
||||||
|
expect(resolveReplyToMode(cfg, "slack", null, "channel")).toBe("first");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("createReplyToModeFilter", () => {
|
describe("createReplyToModeFilter", () => {
|
||||||
|
|||||||
@ -9,12 +9,14 @@ export function resolveReplyToMode(
|
|||||||
cfg: ClawdbotConfig,
|
cfg: ClawdbotConfig,
|
||||||
channel?: OriginatingChannelType,
|
channel?: OriginatingChannelType,
|
||||||
accountId?: string | null,
|
accountId?: string | null,
|
||||||
|
chatType?: string | null,
|
||||||
): ReplyToMode {
|
): ReplyToMode {
|
||||||
const provider = normalizeChannelId(channel);
|
const provider = normalizeChannelId(channel);
|
||||||
if (!provider) return "all";
|
if (!provider) return "all";
|
||||||
const resolved = getChannelDock(provider)?.threading?.resolveReplyToMode?.({
|
const resolved = getChannelDock(provider)?.threading?.resolveReplyToMode?.({
|
||||||
cfg,
|
cfg,
|
||||||
accountId,
|
accountId,
|
||||||
|
chatType,
|
||||||
});
|
});
|
||||||
return resolved ?? "all";
|
return resolved ?? "all";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import type { ClawdbotConfig } from "../config/config.js";
|
|||||||
import { resolveDiscordAccount } from "../discord/accounts.js";
|
import { resolveDiscordAccount } from "../discord/accounts.js";
|
||||||
import { resolveIMessageAccount } from "../imessage/accounts.js";
|
import { resolveIMessageAccount } from "../imessage/accounts.js";
|
||||||
import { resolveSignalAccount } from "../signal/accounts.js";
|
import { resolveSignalAccount } from "../signal/accounts.js";
|
||||||
import { resolveSlackAccount } from "../slack/accounts.js";
|
import { resolveSlackAccount, resolveSlackReplyToMode } from "../slack/accounts.js";
|
||||||
import { buildSlackThreadingToolContext } from "../slack/threading-tool-context.js";
|
import { buildSlackThreadingToolContext } from "../slack/threading-tool-context.js";
|
||||||
import { resolveTelegramAccount } from "../telegram/accounts.js";
|
import { resolveTelegramAccount } from "../telegram/accounts.js";
|
||||||
import { normalizeE164 } from "../utils.js";
|
import { normalizeE164 } from "../utils.js";
|
||||||
@ -224,8 +224,8 @@ const DOCKS: Record<ChatChannelId, ChannelDock> = {
|
|||||||
resolveRequireMention: resolveSlackGroupRequireMention,
|
resolveRequireMention: resolveSlackGroupRequireMention,
|
||||||
},
|
},
|
||||||
threading: {
|
threading: {
|
||||||
resolveReplyToMode: ({ cfg, accountId }) =>
|
resolveReplyToMode: ({ cfg, accountId, chatType }) =>
|
||||||
resolveSlackAccount({ cfg, accountId }).replyToMode ?? "off",
|
resolveSlackReplyToMode(resolveSlackAccount({ cfg, accountId }), chatType),
|
||||||
allowTagsWhenOff: true,
|
allowTagsWhenOff: true,
|
||||||
buildToolContext: (params) => buildSlackThreadingToolContext(params),
|
buildToolContext: (params) => buildSlackThreadingToolContext(params),
|
||||||
},
|
},
|
||||||
|
|||||||
@ -198,6 +198,7 @@ export type ChannelThreadingAdapter = {
|
|||||||
resolveReplyToMode?: (params: {
|
resolveReplyToMode?: (params: {
|
||||||
cfg: ClawdbotConfig;
|
cfg: ClawdbotConfig;
|
||||||
accountId?: string | null;
|
accountId?: string | null;
|
||||||
|
chatType?: string | null;
|
||||||
}) => "off" | "first" | "all";
|
}) => "off" | "first" | "all";
|
||||||
allowTagsWhenOff?: boolean;
|
allowTagsWhenOff?: boolean;
|
||||||
buildToolContext?: (params: {
|
buildToolContext?: (params: {
|
||||||
|
|||||||
@ -117,6 +117,11 @@ export type SlackAccountConfig = {
|
|||||||
reactionAllowlist?: Array<string | number>;
|
reactionAllowlist?: Array<string | number>;
|
||||||
/** Control reply threading when reply tags are present (off|first|all). */
|
/** Control reply threading when reply tags are present (off|first|all). */
|
||||||
replyToMode?: ReplyToMode;
|
replyToMode?: ReplyToMode;
|
||||||
|
/**
|
||||||
|
* Optional per-chat-type reply threading overrides.
|
||||||
|
* Example: { direct: "all", group: "first", channel: "off" }.
|
||||||
|
*/
|
||||||
|
replyToModeByChatType?: Partial<Record<"direct" | "group" | "channel", ReplyToMode>>;
|
||||||
/** Thread session behavior. */
|
/** Thread session behavior. */
|
||||||
thread?: SlackThreadConfig;
|
thread?: SlackThreadConfig;
|
||||||
actions?: SlackActionConfig;
|
actions?: SlackActionConfig;
|
||||||
|
|||||||
@ -280,6 +280,14 @@ export const SlackThreadSchema = z
|
|||||||
})
|
})
|
||||||
.strict();
|
.strict();
|
||||||
|
|
||||||
|
const SlackReplyToModeByChatTypeSchema = z
|
||||||
|
.object({
|
||||||
|
direct: ReplyToModeSchema.optional(),
|
||||||
|
group: ReplyToModeSchema.optional(),
|
||||||
|
channel: ReplyToModeSchema.optional(),
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export const SlackAccountSchema = z
|
export const SlackAccountSchema = z
|
||||||
.object({
|
.object({
|
||||||
name: z.string().optional(),
|
name: z.string().optional(),
|
||||||
@ -307,6 +315,7 @@ export const SlackAccountSchema = z
|
|||||||
reactionNotifications: z.enum(["off", "own", "all", "allowlist"]).optional(),
|
reactionNotifications: z.enum(["off", "own", "all", "allowlist"]).optional(),
|
||||||
reactionAllowlist: z.array(z.union([z.string(), z.number()])).optional(),
|
reactionAllowlist: z.array(z.union([z.string(), z.number()])).optional(),
|
||||||
replyToMode: ReplyToModeSchema.optional(),
|
replyToMode: ReplyToModeSchema.optional(),
|
||||||
|
replyToModeByChatType: SlackReplyToModeByChatTypeSchema.optional(),
|
||||||
thread: SlackThreadSchema.optional(),
|
thread: SlackThreadSchema.optional(),
|
||||||
actions: z
|
actions: z
|
||||||
.object({
|
.object({
|
||||||
|
|||||||
@ -230,6 +230,7 @@ export {
|
|||||||
listSlackAccountIds,
|
listSlackAccountIds,
|
||||||
resolveDefaultSlackAccountId,
|
resolveDefaultSlackAccountId,
|
||||||
resolveSlackAccount,
|
resolveSlackAccount,
|
||||||
|
resolveSlackReplyToMode,
|
||||||
type ResolvedSlackAccount,
|
type ResolvedSlackAccount,
|
||||||
} from "../slack/accounts.js";
|
} from "../slack/accounts.js";
|
||||||
export { slackOnboardingAdapter } from "../channels/plugins/onboarding/slack.js";
|
export { slackOnboardingAdapter } from "../channels/plugins/onboarding/slack.js";
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import type { ClawdbotConfig } from "../config/config.js";
|
import type { ClawdbotConfig } from "../config/config.js";
|
||||||
import type { SlackAccountConfig } from "../config/types.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 { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js";
|
||||||
import { resolveSlackAppToken, resolveSlackBotToken } from "./token.js";
|
import { resolveSlackAppToken, resolveSlackBotToken } from "./token.js";
|
||||||
|
|
||||||
@ -20,6 +21,7 @@ export type ResolvedSlackAccount = {
|
|||||||
reactionNotifications?: SlackAccountConfig["reactionNotifications"];
|
reactionNotifications?: SlackAccountConfig["reactionNotifications"];
|
||||||
reactionAllowlist?: SlackAccountConfig["reactionAllowlist"];
|
reactionAllowlist?: SlackAccountConfig["reactionAllowlist"];
|
||||||
replyToMode?: SlackAccountConfig["replyToMode"];
|
replyToMode?: SlackAccountConfig["replyToMode"];
|
||||||
|
replyToModeByChatType?: SlackAccountConfig["replyToModeByChatType"];
|
||||||
actions?: SlackAccountConfig["actions"];
|
actions?: SlackAccountConfig["actions"];
|
||||||
slashCommand?: SlackAccountConfig["slashCommand"];
|
slashCommand?: SlackAccountConfig["slashCommand"];
|
||||||
dm?: SlackAccountConfig["dm"];
|
dm?: SlackAccountConfig["dm"];
|
||||||
@ -95,6 +97,7 @@ export function resolveSlackAccount(params: {
|
|||||||
reactionNotifications: merged.reactionNotifications,
|
reactionNotifications: merged.reactionNotifications,
|
||||||
reactionAllowlist: merged.reactionAllowlist,
|
reactionAllowlist: merged.reactionAllowlist,
|
||||||
replyToMode: merged.replyToMode,
|
replyToMode: merged.replyToMode,
|
||||||
|
replyToModeByChatType: merged.replyToModeByChatType,
|
||||||
actions: merged.actions,
|
actions: merged.actions,
|
||||||
slashCommand: merged.slashCommand,
|
slashCommand: merged.slashCommand,
|
||||||
dm: merged.dm,
|
dm: merged.dm,
|
||||||
@ -107,3 +110,15 @@ export function listEnabledSlackAccounts(cfg: ClawdbotConfig): ResolvedSlackAcco
|
|||||||
.map((accountId) => resolveSlackAccount({ cfg, accountId }))
|
.map((accountId) => resolveSlackAccount({ cfg, accountId }))
|
||||||
.filter((account) => account.enabled);
|
.filter((account) => account.enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveSlackReplyToMode(
|
||||||
|
account: ResolvedSlackAccount,
|
||||||
|
chatType?: string | null,
|
||||||
|
): "off" | "first" | "all" {
|
||||||
|
const normalized = normalizeChatType(chatType ?? undefined);
|
||||||
|
const overrides = account.replyToModeByChatType;
|
||||||
|
if (normalized && overrides && overrides[normalized] !== undefined) {
|
||||||
|
return overrides[normalized] ?? "off";
|
||||||
|
}
|
||||||
|
return account.replyToMode ?? "off";
|
||||||
|
}
|
||||||
|
|||||||
95
src/slack/threading-tool-context.test.ts
Normal file
95
src/slack/threading-tool-context.test.ts
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
import type { ClawdbotConfig } from "../config/config.js";
|
||||||
|
import { buildSlackThreadingToolContext } from "./threading-tool-context.js";
|
||||||
|
|
||||||
|
const emptyCfg = {} as ClawdbotConfig;
|
||||||
|
|
||||||
|
describe("buildSlackThreadingToolContext", () => {
|
||||||
|
it("uses top-level replyToMode by default", () => {
|
||||||
|
const cfg = {
|
||||||
|
channels: {
|
||||||
|
slack: { replyToMode: "first" },
|
||||||
|
},
|
||||||
|
} as ClawdbotConfig;
|
||||||
|
const result = buildSlackThreadingToolContext({
|
||||||
|
cfg,
|
||||||
|
accountId: null,
|
||||||
|
context: { ChatType: "channel" },
|
||||||
|
});
|
||||||
|
expect(result.replyToMode).toBe("first");
|
||||||
|
});
|
||||||
|
|
||||||
|
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 all mode when ThreadLabel is present", () => {
|
||||||
|
const cfg = {
|
||||||
|
channels: {
|
||||||
|
slack: { replyToMode: "off" },
|
||||||
|
},
|
||||||
|
} as ClawdbotConfig;
|
||||||
|
const result = buildSlackThreadingToolContext({
|
||||||
|
cfg,
|
||||||
|
accountId: null,
|
||||||
|
context: { ChatType: "channel", ThreadLabel: "some-thread" },
|
||||||
|
});
|
||||||
|
expect(result.replyToMode).toBe("all");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("defaults to off when no replyToMode is configured", () => {
|
||||||
|
const result = buildSlackThreadingToolContext({
|
||||||
|
cfg: emptyCfg,
|
||||||
|
accountId: null,
|
||||||
|
context: { ChatType: "direct" },
|
||||||
|
});
|
||||||
|
expect(result.replyToMode).toBe("off");
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -3,7 +3,7 @@ import type {
|
|||||||
ChannelThreadingToolContext,
|
ChannelThreadingToolContext,
|
||||||
} from "../channels/plugins/types.js";
|
} from "../channels/plugins/types.js";
|
||||||
import type { ClawdbotConfig } from "../config/config.js";
|
import type { ClawdbotConfig } from "../config/config.js";
|
||||||
import { resolveSlackAccount } from "./accounts.js";
|
import { resolveSlackAccount, resolveSlackReplyToMode } from "./accounts.js";
|
||||||
|
|
||||||
export function buildSlackThreadingToolContext(params: {
|
export function buildSlackThreadingToolContext(params: {
|
||||||
cfg: ClawdbotConfig;
|
cfg: ClawdbotConfig;
|
||||||
@ -11,11 +11,11 @@ export function buildSlackThreadingToolContext(params: {
|
|||||||
context: ChannelThreadingContext;
|
context: ChannelThreadingContext;
|
||||||
hasRepliedRef?: { value: boolean };
|
hasRepliedRef?: { value: boolean };
|
||||||
}): ChannelThreadingToolContext {
|
}): ChannelThreadingToolContext {
|
||||||
const configuredReplyToMode =
|
const account = resolveSlackAccount({
|
||||||
resolveSlackAccount({
|
cfg: params.cfg,
|
||||||
cfg: params.cfg,
|
accountId: params.accountId,
|
||||||
accountId: params.accountId,
|
});
|
||||||
}).replyToMode ?? "off";
|
const configuredReplyToMode = resolveSlackReplyToMode(account, params.context.ChatType);
|
||||||
const effectiveReplyToMode = params.context.ThreadLabel ? "all" : configuredReplyToMode;
|
const effectiveReplyToMode = params.context.ThreadLabel ? "all" : configuredReplyToMode;
|
||||||
const threadId = params.context.MessageThreadId ?? params.context.ReplyToId;
|
const threadId = params.context.MessageThreadId ?? params.context.ReplyToId;
|
||||||
return {
|
return {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user