using System.Diagnostics; using AxCopilot.SDK; using AxCopilot.Services; using AxCopilot.Themes; namespace AxCopilot.Handlers; /// /// 실행 중인 프로세스를 검색하고 종료합니다. "kill " 프리픽스로 사용합니다. /// 예: kill chrome → Chrome 프로세스 목록 표시 후 선택하면 종료 /// kill → 현재 실행 중인 모든 사용자 프로세스 표시 /// public class ProcessHandler : IActionHandler { public string? Prefix => "kill "; // 뒤에 공백 포함 — 오탐 방지 public PluginMetadata Metadata => new( "ProcessKiller", "프로세스 종료 — kill 뒤에 프로세스명 입력", "1.0", "AX"); // 시스템 핵심 프로세스 보호 목록 (종료 방지) private static readonly HashSet ProtectedProcesses = new(StringComparer.OrdinalIgnoreCase) { "system", "smss", "csrss", "wininit", "winlogon", "services", "lsass", "svchost", "explorer", "dwm", "fontdrvhost", "spoolsv", "registry", }; public Task> GetItemsAsync(string query, CancellationToken ct) { if (string.IsNullOrWhiteSpace(query)) { return Task.FromResult>( [ new LauncherItem( "종료할 프로세스명을 입력하세요", "예: kill chrome · kill notepad · kill explorer", null, null, Symbol: Symbols.Power) ]); } var q = query.Trim().ToLowerInvariant(); try { var processes = Process.GetProcesses() .Where(p => !ProtectedProcesses.Contains(p.ProcessName) && p.ProcessName.ToLowerInvariant().Contains(q)) .OrderBy(p => p.ProcessName) .Take(12) .ToList(); if (processes.Count == 0) { return Task.FromResult>( [ new LauncherItem( $"'{query}' 프로세스를 찾을 수 없습니다", "실행 중인 프로세스가 없거나 이름이 다릅니다", null, null, Symbol: Symbols.Warning) ]); } // 같은 이름의 프로세스 묶기 var grouped = processes .GroupBy(p => p.ProcessName, StringComparer.OrdinalIgnoreCase) .Select(g => { var pids = g.Select(p => p.Id).ToList(); var memMb = g.Sum(p => { try { return p.WorkingSet64 / 1024 / 1024; } catch { return 0L; } }); var title = g.Count() > 1 ? $"{g.Key} ({g.Count()}개 인스턴스)" : g.Key; var subtitle = $"PID: {string.Join(", ", pids)} · 메모리: {memMb} MB · Enter로 종료"; return new LauncherItem( title, subtitle, null, new ProcessKillData(g.Key, pids), Symbol: Symbols.Power); }) .ToList(); return Task.FromResult>(grouped); } catch (Exception ex) { LogService.Warn($"프로세스 목록 조회 실패: {ex.Message}"); return Task.FromResult>( [ new LauncherItem("프로세스 목록 조회 실패", ex.Message, null, null, Symbol: Symbols.Error) ]); } } public Task ExecuteAsync(LauncherItem item, CancellationToken ct) { if (item.Data is not ProcessKillData data) return Task.CompletedTask; int killed = 0, failed = 0; foreach (var pid in data.Pids) { try { var proc = Process.GetProcessById(pid); proc.Kill(entireProcessTree: false); killed++; } catch { failed++; } } LogService.Info($"프로세스 종료: {data.Name} — {killed}개 성공, {failed}개 실패"); return Task.CompletedTask; } private record ProcessKillData(string Name, List Pids); }