using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.Json; using System.Windows; using System.Windows.Threading; namespace AxCopilot.Services; public class AgentTriggerService : IDisposable { private readonly SettingsService _settings; private FileSystemWatcher? _fileWatcher; private DispatcherTimer? _scheduleTimer; private readonly List _rules = new List(); private bool _disposed; public Action? OnTriggerFired { get; set; } public IReadOnlyList Rules => _rules; public AgentTriggerService(SettingsService settings) { _settings = settings; } public void Start() { LoadRules(); StartFileWatcher(); StartScheduleTimer(); LogService.Info($"에이전트 트리거 서비스 시작: {_rules.Count}개 규칙"); } public void Reload() { Stop(); Start(); } public void Stop() { _fileWatcher?.Dispose(); _fileWatcher = null; DispatcherTimer? scheduleTimer = _scheduleTimer; if (scheduleTimer != null) { scheduleTimer.Stop(); } } private void LoadRules() { _rules.Clear(); string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AxCopilot", "triggers.json"); if (!File.Exists(path)) { return; } try { string json = File.ReadAllText(path); List list = JsonSerializer.Deserialize>(json); if (list != null) { _rules.AddRange(list.Where((TriggerRule r) => r.Enabled)); } } catch (Exception ex) { LogService.Warn("트리거 규칙 로드 실패: " + ex.Message); } } private void StartFileWatcher() { List fileRules = _rules.Where((TriggerRule r) => r.Type == "file_change").ToList(); if (fileRules.Count == 0) { return; } string workFolder = _settings.Settings.Llm.WorkFolder; if (string.IsNullOrEmpty(workFolder) || !Directory.Exists(workFolder)) { return; } _fileWatcher = new FileSystemWatcher(workFolder) { IncludeSubdirectories = true, NotifyFilter = (NotifyFilters.FileName | NotifyFilters.LastWrite), EnableRaisingEvents = true }; DateTime lastTrigger = DateTime.MinValue; _fileWatcher.Changed += delegate(object _, FileSystemEventArgs e) { if ((DateTime.Now - lastTrigger).TotalSeconds < 5.0) { return; } foreach (TriggerRule rule in fileRules) { if (MatchesPattern(e.FullPath, rule.Pattern)) { lastTrigger = DateTime.Now; string prompt = rule.Prompt.Replace("{file}", e.FullPath).Replace("{name}", e.Name ?? ""); Application current = Application.Current; if (current != null) { ((DispatcherObject)current).Dispatcher.BeginInvoke((Delegate)(Action)delegate { OnTriggerFired?.Invoke(rule.Name, prompt); }, Array.Empty()); } break; } } }; } private void StartScheduleTimer() { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Expected O, but got Unknown List scheduleRules = _rules.Where((TriggerRule r) => r.Type == "schedule").ToList(); if (scheduleRules.Count == 0) { return; } _scheduleTimer = new DispatcherTimer { Interval = TimeSpan.FromMinutes(1.0) }; _scheduleTimer.Tick += delegate { DateTime now = DateTime.Now; foreach (TriggerRule item in scheduleRules) { if (ShouldRunSchedule(item, now)) { item.LastRun = now; OnTriggerFired?.Invoke(item.Name, item.Prompt); } } }; _scheduleTimer.Start(); } private static bool MatchesPattern(string filePath, string? pattern) { if (string.IsNullOrEmpty(pattern)) { return true; } if (pattern.StartsWith("*.")) { return filePath.EndsWith(pattern.Substring(1, pattern.Length - 1), StringComparison.OrdinalIgnoreCase); } return filePath.Contains(pattern, StringComparison.OrdinalIgnoreCase); } private static bool ShouldRunSchedule(TriggerRule rule, DateTime now) { if (rule.LastRun.HasValue && (now - rule.LastRun.Value).TotalMinutes < (double)(rule.IntervalMinutes ?? 60)) { return false; } if (rule.ActiveHourStart.HasValue && now.Hour < rule.ActiveHourStart.Value) { return false; } if (rule.ActiveHourEnd.HasValue && now.Hour >= rule.ActiveHourEnd.Value) { return false; } return true; } public void FireManual(string ruleName) { TriggerRule triggerRule = _rules.FirstOrDefault((TriggerRule r) => r.Name == ruleName); if (triggerRule != null) { OnTriggerFired?.Invoke(triggerRule.Name, triggerRule.Prompt); } } public void Dispose() { if (!_disposed) { _disposed = true; Stop(); } } }