export type WhisperGateConfig = { enabled?: boolean; discordOnly?: boolean; listMode?: "human-list" | "agent-list"; humanList?: string[]; agentList?: string[]; channelPolicies?: Record< string, { listMode?: "human-list" | "agent-list"; humanList?: string[]; agentList?: string[]; endSymbols?: string[]; } >; // backward compatibility bypassUserIds?: string[]; endSymbols?: string[]; noReplyProvider: string; noReplyModel: string; }; export type Decision = { shouldUseNoReply: boolean; reason: string; }; function getLastChar(input: string): string { const t = input.trim(); return t.length ? t[t.length - 1] : ""; } function resolvePolicy(config: WhisperGateConfig, channelId?: string) { const globalMode = config.listMode || "human-list"; const globalHuman = config.humanList || config.bypassUserIds || []; const globalAgent = config.agentList || []; const globalEnd = config.endSymbols || ["🔚"]; if (!channelId) { return { listMode: globalMode, humanList: globalHuman, agentList: globalAgent, endSymbols: globalEnd }; } const cp = config.channelPolicies || {}; const scoped = cp[channelId]; if (!scoped) { return { listMode: globalMode, humanList: globalHuman, agentList: globalAgent, endSymbols: globalEnd }; } return { listMode: scoped.listMode || globalMode, humanList: scoped.humanList || globalHuman, agentList: scoped.agentList || globalAgent, endSymbols: scoped.endSymbols || globalEnd, }; } export function evaluateDecision(params: { config: WhisperGateConfig; channel?: string; channelId?: string; senderId?: string; content?: string; }): Decision { const { config } = params; if (config.enabled === false) { return { shouldUseNoReply: false, reason: "disabled" }; } const channel = (params.channel || "").toLowerCase(); if (config.discordOnly !== false && channel !== "discord") { return { shouldUseNoReply: false, reason: "non_discord" }; } const policy = resolvePolicy(config, params.channelId); const mode = policy.listMode; const humanList = policy.humanList; const agentList = policy.agentList; const senderId = params.senderId || ""; const inHumanList = !!senderId && humanList.includes(senderId); const inAgentList = !!senderId && agentList.includes(senderId); const lastChar = getLastChar(params.content || ""); const hasEnd = !!lastChar && policy.endSymbols.includes(lastChar); if (mode === "human-list") { if (inHumanList) { return { shouldUseNoReply: false, reason: "human_list_sender" }; } if (hasEnd) { return { shouldUseNoReply: false, reason: `end_symbol:${lastChar}` }; } return { shouldUseNoReply: true, reason: "rule_match_no_end_symbol" }; } // agent-list mode: listed senders require end symbol; others bypass requirement. if (!inAgentList) { return { shouldUseNoReply: false, reason: "non_agent_list_sender" }; } if (hasEnd) { return { shouldUseNoReply: false, reason: `end_symbol:${lastChar}` }; } return { shouldUseNoReply: true, reason: "agent_list_missing_end_symbol" }; }