/** * Auditor Scheduler Service * * Manages the lifecycle and triggering of the Crewly Auditor agent. * The auditor runs as a Claude Code PTY session — initialized at server start * and kept alive until the service stops. * * Three trigger layers: * L1 — Periodic: setInterval every AUDIT_INTERVAL_MS (15 min) * L2 — Event-driven: EventBus events (agent:inactive, task:failed) with debounce * L3 — API: POST /api/auditor/trigger manual trigger * * Additionally supports direct Slack conversation via handleUserMessage(). * * @module services/agent/auditor-scheduler */ import type { EventBusService } from '../event-bus/event-bus.service.js'; import type { AgentRegistrationService } from './agent-registration.service.js'; /** * Status of the AuditorSchedulerService. */ export type AuditorSchedulerStatus = 'stopped' | 'idle' | 'running_audit'; /** * Result from a trigger attempt. */ export interface AuditTriggerResult { /** Whether the audit was started */ triggered: boolean; /** Reason if not triggered */ reason?: string; /** Trigger source */ source: 'periodic' | 'event' | 'api'; /** Timestamp */ timestamp: string; } /** * Slack context for user message routing. */ export interface SlackContext { channelId: string; threadTs: string; } /** * A scheduled audit item that may need catch-up on restart. */ export interface ScheduledAuditItem { /** Unique identifier */ id: string; /** When the audit was scheduled to fire */ scheduledTime: number; /** Source of the schedule */ source: AuditTriggerResult['source']; /** Whether the audit was actually fired */ fired: boolean; } /** * Scheduler service that orchestrates Auditor agent lifecycle via Claude Code PTY session. * * Always-active mode: the auditor PTY session is created at start() * and remains alive until stop(). Audit runs send messages to the existing session. * * Singleton — use AuditorSchedulerService.getInstance(). * * @example * ```typescript * const scheduler = AuditorSchedulerService.getInstance(); * scheduler.setAgentRegistrationService(agentRegService); * scheduler.setEventBusService(eventBus); * scheduler.start(); * ``` */ export declare class AuditorSchedulerService { private static instance; private logger; private status; private agentRegistrationService; private eventBusService; private periodicTimer; private eventDebounceTimer; private eventListenerBound; private sessionRecoveryTimer; private auditTimeoutTimer; private lastAuditStart; private auditCount; private sessionReady; private sessionInitializing; private sessionRetryCount; private sessionCooldownUntil; private scheduledItems; private constructor(); /** * Get the singleton instance. * * @returns AuditorSchedulerService instance */ static getInstance(): AuditorSchedulerService; /** * Reset the singleton (for testing). */ static resetInstance(): void; /** * Set the AgentRegistrationService for PTY session management. * * @param service - The AgentRegistrationService instance */ setAgentRegistrationService(service: AgentRegistrationService): void; /** * Set the EventBusService for L2 event-driven triggers. * * @param eventBus - The EventBusService instance */ setEventBusService(eventBus: EventBusService): void; /** * Start all trigger layers and initialize the auditor PTY session. * The auditor session is created immediately (always-active mode). * L1: periodic interval. L2: EventBus listener. */ start(): void; /** * Stop all trigger layers, clean up timers, and terminate the PTY session. * Only called when the entire service is shutting down. */ stop(): void; /** * Trigger an audit run. * * Checks if an audit is already running, ensures the auditor session exists, * sends the audit command via PTY, and keeps the session alive after completion. * * @param source - What triggered this audit ('periodic', 'event', 'api') * @returns Result indicating whether the audit was triggered */ trigger(source: AuditTriggerResult['source']): Promise; /** * Handle a user message routed from Slack. * * Passes the message to the auditor PTY session with Slack context * so the auditor can reply directly via the reply_slack tool. * * @param message - User message text (auditor prefix already stripped) * @param slackContext - Slack channel and thread identifiers * @returns Result indicating whether the message was handled */ handleUserMessage(message: string, slackContext: SlackContext): Promise; /** * Get the current scheduler status and statistics. * * @returns Status object */ getStatus(): { status: AuditorSchedulerStatus; auditCount: number; lastAuditStart: string | null; periodicEnabled: boolean; eventListenerBound: boolean; runtimeReady: boolean; }; /** * Add a scheduled audit item for future tracking. * Items that are overdue at next startup will be caught up. * * @param scheduledTime - Unix timestamp when the audit should fire * @param source - Trigger source * @returns The created ScheduledAuditItem */ addScheduledItem(scheduledTime: number, source: AuditTriggerResult['source']): ScheduledAuditItem; /** * Get all scheduled items (for testing/diagnostics). * * @returns Copy of the scheduled items array */ getScheduledItems(): ScheduledAuditItem[]; /** * Check for scheduled items where scheduledTime < now and status !== fired, * then fire them immediately. Called on startup/restore to catch up on any * audits that were missed during downtime. * * @returns Number of missed checks that were caught up */ catchUpMissedChecks(): Promise; /** * Ensure the auditor Claude Code PTY session exists. * Creates a new session via AgentRegistrationService if not ready. * Guards against concurrent initialization and enforces retry limits * with exponential backoff to prevent restart loops. */ private ensureAuditorSession; /** * Bind the EventBus listener for L2 event-driven triggers. * Listens for agent:inactive and task:failed events with debounce. * Uses event_published (fires on ALL published events) instead of * event_delivered (only fires when a subscription match exists). */ private bindEventBusListener; } //# sourceMappingURL=auditor-scheduler.service.d.ts.map