/** * AI Hooks Service — intelligent email triage via MCP sampling. * * Listens for new email events on the event bus and: * - Matches static rules FIRST (fast, free, deterministic) * - Falls through to AI triage via `sampling/createMessage` if no rule matched * - Uses preset system prompts + custom instructions for AI triage * - Auto-applies labels and flags based on AI response * - Falls back to logging if sampling is unavailable */ import type { Server } from '@modelcontextprotocol/server'; import type { EmailMeta, HookRule, HooksConfig } from '../types/index.js'; import type ImapService from './imap.service.js'; import NotifierService from './notifier.service.js'; interface TriageResult { priority?: 'urgent' | 'high' | 'normal' | 'low'; labels?: string[]; flag?: boolean; action?: string; addToCalendar?: boolean; } interface BatchEmail { account: string; mailbox: string; meta: EmailMeta; } interface RuleMatchResult { matched: true; rule: HookRule; } interface RuleNoMatch { matched: false; } type StaticMatchOutcome = RuleMatchResult | RuleNoMatch; export default class HooksService { private config; /** Whether calendar-event creation may save email attachments locally. */ private readonly autoDownloadAttachments; private imapService; private lowLevelServer; /** * Latches to `true` once a sampling request fails structurally (the client * has no sampling capability, or is a 2026-07-28-era connection where the * server→client request channel was removed — SEP-2577). Subsequent batches * then skip straight to notify instead of re-attempting a doomed request. * Transient failures do not latch. */ private samplingUnavailable; private pendingEmails; private batchTimer; private rateCounter; private rateResetTimer; private readonly resolvedSystemPrompt; private readonly notifier; private readonly localCalendar; private static readonly MAX_SAMPLING_PER_MIN; /** * Named handler so stop() can detach exactly this subscription. Other * services (sync engine, verification catcher) share the same channel, so * removeAllListeners here would silently unsubscribe them. */ private readonly onNewEmailHandler; constructor(config: HooksConfig, imapService: ImapService, opts?: { autoDownloadAttachments: boolean; }); /** Returns the NotifierService instance for direct tool access. */ getNotifier(): NotifierService; /** Returns the current hooks configuration. */ getHooksConfig(): HooksConfig; /** * Start listening for email events. * * Pass the low-level MCP `Server` (from `mcpServer.server`) when one is * available — it is used for resource-update notifications and, on legacy * connections, opportunistic AI-triage sampling. Pass `null` for transports * with no persistent connection (stateless HTTP), where notifications and * sampling do not apply and triage degrades to notify. */ start(lowLevelServer: Server | null): void; stop(): void; private onNewEmail; private flushBatch; static matchStaticRules(email: BatchEmail, rules: HookRule[]): StaticMatchOutcome; private static emailMatchesRule; private applyStaticRule; private sendResourceUpdates; private notifyBatch; private triageBatch; private static formatEmailSummary; private applyTriageResults; private applySingleTriage; private applyCalendarAction; static parseTriageResponse(text: string, expectedCount: number): TriageResult[]; static sanitizeTriageResult(raw: unknown): TriageResult; } export {}; //# sourceMappingURL=hooks.service.d.ts.map