export type WhisperGateConfig = { enabled?: boolean; discordOnly?: boolean; listMode?: "human-list" | "agent-list"; humanList?: string[]; agentList?: 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] : ""; } export function evaluateDecision(params: { config: WhisperGateConfig; channel?: 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 mode = config.listMode || "human-list"; const humanList = config.humanList || config.bypassUserIds || []; const agentList = config.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 && (config.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" }; }