import type { ClawdbotConfig } from "../config/config.js"; import type { SlackAccountConfig } from "../config/types.js"; import { DEFAULT_ACCOUNT_ID, normalizeAccountId, } from "../routing/session-key.js"; import { resolveSlackAppToken, resolveSlackBotToken } from "./token.js"; export type SlackTokenSource = "env" | "config" | "none"; export type ResolvedSlackAccount = { accountId: string; enabled: boolean; name?: string; botToken?: string; appToken?: string; botTokenSource: SlackTokenSource; appTokenSource: SlackTokenSource; config: SlackAccountConfig; groupPolicy?: SlackAccountConfig["groupPolicy"]; textChunkLimit?: SlackAccountConfig["textChunkLimit"]; mediaMaxMb?: SlackAccountConfig["mediaMaxMb"]; reactionNotifications?: SlackAccountConfig["reactionNotifications"]; reactionAllowlist?: SlackAccountConfig["reactionAllowlist"]; replyToMode?: SlackAccountConfig["replyToMode"]; actions?: SlackAccountConfig["actions"]; slashCommand?: SlackAccountConfig["slashCommand"]; dm?: SlackAccountConfig["dm"]; channels?: SlackAccountConfig["channels"]; }; function listConfiguredAccountIds(cfg: ClawdbotConfig): string[] { const accounts = cfg.slack?.accounts; if (!accounts || typeof accounts !== "object") return []; return Object.keys(accounts).filter(Boolean); } export function listSlackAccountIds(cfg: ClawdbotConfig): string[] { const ids = listConfiguredAccountIds(cfg); if (ids.length === 0) return [DEFAULT_ACCOUNT_ID]; return ids.sort((a, b) => a.localeCompare(b)); } export function resolveDefaultSlackAccountId(cfg: ClawdbotConfig): string { const ids = listSlackAccountIds(cfg); if (ids.includes(DEFAULT_ACCOUNT_ID)) return DEFAULT_ACCOUNT_ID; return ids[0] ?? DEFAULT_ACCOUNT_ID; } function resolveAccountConfig( cfg: ClawdbotConfig, accountId: string, ): SlackAccountConfig | undefined { const accounts = cfg.slack?.accounts; if (!accounts || typeof accounts !== "object") return undefined; return accounts[accountId] as SlackAccountConfig | undefined; } function mergeSlackAccountConfig( cfg: ClawdbotConfig, accountId: string, ): SlackAccountConfig { const { accounts: _ignored, ...base } = (cfg.slack ?? {}) as SlackAccountConfig & { accounts?: unknown }; const account = resolveAccountConfig(cfg, accountId) ?? {}; return { ...base, ...account }; } export function resolveSlackAccount(params: { cfg: ClawdbotConfig; accountId?: string | null; }): ResolvedSlackAccount { const accountId = normalizeAccountId(params.accountId); const baseEnabled = params.cfg.slack?.enabled !== false; const merged = mergeSlackAccountConfig(params.cfg, accountId); const accountEnabled = merged.enabled !== false; const enabled = baseEnabled && accountEnabled; const allowEnv = accountId === DEFAULT_ACCOUNT_ID; const envBot = allowEnv ? resolveSlackBotToken(process.env.SLACK_BOT_TOKEN) : undefined; const envApp = allowEnv ? resolveSlackAppToken(process.env.SLACK_APP_TOKEN) : undefined; const configBot = resolveSlackBotToken(merged.botToken); const configApp = resolveSlackAppToken(merged.appToken); const botToken = configBot ?? envBot; const appToken = configApp ?? envApp; const botTokenSource: SlackTokenSource = configBot ? "config" : envBot ? "env" : "none"; const appTokenSource: SlackTokenSource = configApp ? "config" : envApp ? "env" : "none"; return { accountId, enabled, name: merged.name?.trim() || undefined, botToken, appToken, botTokenSource, appTokenSource, config: merged, groupPolicy: merged.groupPolicy, textChunkLimit: merged.textChunkLimit, mediaMaxMb: merged.mediaMaxMb, reactionNotifications: merged.reactionNotifications, reactionAllowlist: merged.reactionAllowlist, replyToMode: merged.replyToMode, actions: merged.actions, slashCommand: merged.slashCommand, dm: merged.dm, channels: merged.channels, }; } export function listEnabledSlackAccounts( cfg: ClawdbotConfig, ): ResolvedSlackAccount[] { return listSlackAccountIds(cfg) .map((accountId) => resolveSlackAccount({ cfg, accountId })) .filter((account) => account.enabled); }