using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Threading; using AxCopilot.Models; using AxCopilot.Services; using AxCopilot.Services.Agent; namespace AxCopilot.Views; public partial class ChatWindow { // ─── Phase 34: Cowork/Code 공통 에이전트 루프 실행 ────────────────── /// /// Cowork/Code 탭 공통 에이전트 루프 실행. /// 기존 Cowork/Code 분기의 중복 코드(~70줄)를 단일 메서드로 통합. /// private async Task RunAgentLoopAsync(string tab, List sendMessages, CancellationToken ct) { OpenWorkflowAnalyzerIfEnabled(); _agentCumulativeInputTokens = 0; _agentCumulativeOutputTokens = 0; // 탭별 시스템 프롬프트 삽입 var systemPrompt = tab == "Code" ? BuildCodeSystemPrompt() : BuildCoworkSystemPrompt(); if (!string.IsNullOrEmpty(systemPrompt)) sendMessages.Insert(0, new ChatMessage { Role = "system", Content = systemPrompt }); _agentLoop.ActiveTab = tab; _agentLoop.EventOccurred += OnAgentEvent; _agentLoop.UserDecisionCallback = CreatePlanDecisionCallback(); try { var response = await _agentLoop.RunAsync(sendMessages, ct); // 완료 알림 if (Llm.NotifyOnComplete) { var label = tab == "Code" ? "AX Code Agent" : "AX Cowork Agent"; var desc = tab == "Code" ? "코드 작업이 완료되었습니다." : "코워크 작업이 완료되었습니다."; Services.NotificationService.Notify(label, desc); } return response; } finally { _agentLoop.EventOccurred -= OnAgentEvent; _agentLoop.UserDecisionCallback = null; } } // ─── 코워크 에이전트 지원 ──────────────────────────────────────────── private string BuildCoworkSystemPrompt() { var workFolder = GetCurrentWorkFolder(); var llm = Llm; var sb = new System.Text.StringBuilder(); sb.AppendLine("You are AX Copilot Agent. You can read, write, and edit files using the provided tools."); sb.AppendLine($"Today's date: {DateTime.Now:yyyy년 M월 d일} ({DateTime.Now:yyyy-MM-dd}, {DateTime.Now:dddd})."); sb.AppendLine("Available skills: excel_create (.xlsx), docx_create (.docx), csv_create (.csv), markdown_create (.md), html_create (.html), script_create (.bat/.ps1), document_review (품질 검증), format_convert (포맷 변환)."); sb.AppendLine("Always explain your plan step by step BEFORE executing tools. After creating files, summarize what was created."); sb.AppendLine("IMPORTANT: When creating documents with dates, always use today's actual date above. Never use placeholder or fictional dates."); sb.AppendLine("IMPORTANT: When asked to create a document with multiple sections (reports, proposals, analyses, etc.), you MUST:"); sb.AppendLine(" 1. First, plan the document: decide the exact sections (headings), their order, and key points for each section based on the topic."); sb.AppendLine(" 2. Call document_plan with sections_hint = your planned section titles (comma-separated). Example: sections_hint=\"회사 개요, 사업 현황, 재무 분석, SWOT, 전략 제언, 결론\""); sb.AppendLine(" This ensures the document structure matches YOUR plan, not a generic template."); sb.AppendLine(" 3. Then immediately call html_create (or docx_create/file_write) using the scaffold from document_plan."); sb.AppendLine(" 4. Write actual detailed content for EVERY section — no skipping, no placeholders, no minimal content."); sb.AppendLine(" 5. Do NOT call html_create directly without document_plan for multi-section documents."); // 문서 품질 검증 루프 sb.AppendLine("\n## Document Quality Review"); sb.AppendLine("After creating any document (html_create, docx_create, excel_create, etc.), you MUST perform a self-review:"); sb.AppendLine("1. Use file_read to read the generated file and verify the content is complete"); sb.AppendLine("2. Check for logical errors: incorrect dates, inconsistent data, missing sections, broken formatting"); sb.AppendLine("3. Verify all requested topics/sections from the user's original request are covered"); sb.AppendLine("4. If issues found, fix them using file_write or file_edit, then re-verify"); sb.AppendLine("5. Report the review result to the user: what was checked and whether corrections were made"); // 문서 포맷 변환 지원 sb.AppendLine("\n## Format Conversion"); sb.AppendLine("When the user requests format conversion (e.g., HTML→Word, Excel→CSV, Markdown→HTML):"); sb.AppendLine("1. Use file_read or document_read to read the source file content"); sb.AppendLine("2. Create a new file in the target format using the appropriate skill (docx_create, html_create, etc.)"); sb.AppendLine("3. Preserve the content structure, formatting, and data as closely as possible"); // 사용자 지정 출력 포맷 var fmt = llm.DefaultOutputFormat; if (!string.IsNullOrEmpty(fmt) && fmt != "auto") { var fmtMap = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["xlsx"] = "Excel (.xlsx) using excel_create", ["docx"] = "Word (.docx) using docx_create", ["html"] = "HTML (.html) using html_create", ["md"] = "Markdown (.md) using markdown_create", ["csv"] = "CSV (.csv) using csv_create", }; if (fmtMap.TryGetValue(fmt, out var fmtDesc)) sb.AppendLine($"IMPORTANT: User prefers output format: {fmtDesc}. Use this format unless the user specifies otherwise."); } // 디자인 무드 — HTML 문서 생성 시 mood 파라미터로 전달하도록 안내 if (!string.IsNullOrEmpty(_selectedMood) && _selectedMood != "modern") sb.AppendLine($"When creating HTML documents with html_create, use mood=\"{_selectedMood}\" for the design template."); else sb.AppendLine("When creating HTML documents with html_create, you can set 'mood' parameter: modern, professional, creative, minimal, elegant, dark, colorful, corporate, magazine, dashboard."); if (!string.IsNullOrEmpty(workFolder)) sb.AppendLine($"Current work folder: {workFolder}"); sb.AppendLine($"File permission mode: {llm.FilePermission}"); // 폴더 데이터 활용 지침 switch (_folderDataUsage) { case "active": sb.AppendLine("IMPORTANT: Folder Data Usage = ACTIVE. You have 'document_read' and 'folder_map' tools available."); sb.AppendLine("Before creating reports, use folder_map to scan the work folder structure. " + "Then EVALUATE whether each document is RELEVANT to the user's current request topic. " + "Only use document_read on files that are clearly related to the conversation subject. " + "Do NOT read or reference files that are unrelated to the user's request, even if they exist in the folder. " + "In your planning step, list which files you plan to read and explain WHY they are relevant."); break; case "passive": sb.AppendLine("Folder Data Usage = PASSIVE. You have 'document_read' and 'folder_map' tools. " + "Only read folder documents when the user explicitly asks you to reference or use them."); break; default: // "none" sb.AppendLine("Folder Data Usage = NONE. Do NOT read or reference documents in the work folder unless the user explicitly provides a file path."); break; } // 프리셋 시스템 프롬프트가 있으면 추가 lock (_convLock) { if (_currentConversation != null && !string.IsNullOrEmpty(_currentConversation.SystemCommand)) sb.AppendLine("\n" + _currentConversation.SystemCommand); } // 프로젝트 문맥 파일 (AX.md) 주입 — Phase 27-C: @include 지시어 해석 포함 sb.Append(LoadProjectContext(workFolder)); // 프로젝트 규칙 (.ax/rules/) 자동 주입 sb.Append(BuildProjectRulesSection(workFolder)); // 에이전트 메모리 주입 sb.Append(BuildMemorySection(workFolder)); // 피드백 학습 컨텍스트 주입 sb.Append(BuildFeedbackContext()); // Phase 27-B: 현재 파일 경로 기반 자동 스킬 주입 sb.Append(BuildPathBasedSkillSection()); return sb.ToString(); } private string BuildCodeSystemPrompt() { var workFolder = GetCurrentWorkFolder(); var llm = Llm; var code = llm.Code; var sb = new System.Text.StringBuilder(); sb.AppendLine("You are AX Copilot Code Agent — a senior software engineer for enterprise development."); sb.AppendLine($"Today's date: {DateTime.Now:yyyy년 M월 d일} ({DateTime.Now:yyyy-MM-dd})."); sb.AppendLine("Available tools: file_read, file_write, file_edit (supports replace_all), glob, grep (supports context_lines, case_sensitive), folder_map, process, dev_env_detect, build_run, git_tool."); sb.AppendLine("IMPORTANT: When creating documents with dates, always use today's actual date above."); sb.AppendLine("\n## Core Workflow (MANDATORY — follow this order)"); sb.AppendLine("1. ORIENT: Run folder_map (depth=2) to understand project structure. Check .gitignore, README, config files."); sb.AppendLine("2. BASELINE: If tests exist, run build_run action='test' FIRST to establish baseline. Record pass/fail count."); sb.AppendLine("3. ANALYZE: Use grep (with context_lines=2) + file_read to deeply understand the code you'll modify."); sb.AppendLine(" - Always check callers/references: grep for function/class names to find all usage points."); sb.AppendLine(" - Read test files related to the code you're changing to understand expected behavior."); sb.AppendLine("4. PLAN: Present your analysis + impact assessment. List ALL files that will be modified."); sb.AppendLine(" - Explain WHY each change is needed and what could break."); sb.AppendLine(" - Wait for user approval before proceeding."); sb.AppendLine("5. IMPLEMENT: Apply changes using file_edit (preferred — shows diff). Use file_write only for new files."); sb.AppendLine(" - Make the MINIMUM changes needed. Don't refactor unrelated code."); sb.AppendLine(" - Prefer file_edit with replace_all=false for precision edits."); sb.AppendLine("6. VERIFY: Run build_run action='build' then action='test'. Compare results with baseline."); sb.AppendLine(" - If tests fail that passed before, fix immediately."); sb.AppendLine(" - If build fails, analyze error output and correct."); sb.AppendLine("7. GIT: Use git_tool to check status, create diff, and optionally commit."); sb.AppendLine("8. REPORT: Summarize changes, test results, and any remaining concerns."); sb.AppendLine("\n## Development Environment"); sb.AppendLine("Use dev_env_detect to check installed IDEs, runtimes, and build tools before running commands."); sb.AppendLine("IMPORTANT: Do NOT attempt to install compilers, IDEs, or build tools. Only use what is already installed."); // 패키지 저장소 정보 sb.AppendLine("\n## Package Repositories"); if (!string.IsNullOrEmpty(code.NexusBaseUrl)) sb.AppendLine($"Enterprise Nexus: {code.NexusBaseUrl}"); sb.AppendLine($"NuGet (.NET): {code.NugetSource}"); sb.AppendLine($"PyPI/Conda (Python): {code.PypiSource}"); sb.AppendLine($"Maven (Java): {code.MavenSource}"); sb.AppendLine($"npm (JavaScript): {code.NpmSource}"); sb.AppendLine("When adding dependencies, use these repository URLs."); // IDE 정보 if (!string.IsNullOrEmpty(code.PreferredIdePath)) sb.AppendLine($"\nPreferred IDE: {code.PreferredIdePath}"); // 사용자 선택 개발 언어 if (_selectedLanguage != "auto") { var langName = _selectedLanguage switch { "python" => "Python", "java" => "Java", "csharp" => "C# (.NET)", "cpp" => "C/C++", "javascript" => "JavaScript/TypeScript", _ => _selectedLanguage }; sb.AppendLine($"\nIMPORTANT: User selected language: {langName}. Prioritize this language for code analysis and generation."); } // 언어별 가이드라인 sb.AppendLine("\n## Language Guidelines"); sb.AppendLine("- C# (.NET): Use dotnet CLI. NuGet for packages. Follow Microsoft naming conventions."); sb.AppendLine("- Python: Use conda/pip. Follow PEP8. Use type hints. Virtual env preferred."); sb.AppendLine("- Java: Use Maven/Gradle. Follow Google Java Style Guide."); sb.AppendLine("- C++: Use CMake for build. Follow C++ Core Guidelines."); sb.AppendLine("- JavaScript/TypeScript: Use npm/yarn. Follow ESLint rules. Vue3 uses Composition API."); // 코드 품질 + 안전 수칙 sb.AppendLine("\n## Code Quality & Safety"); sb.AppendLine("- NEVER delete or overwrite files without user confirmation."); sb.AppendLine("- ALWAYS read a file before editing it. Don't guess contents."); sb.AppendLine("- Prefer file_edit over file_write for existing files (shows diff)."); sb.AppendLine("- Use grep to find ALL references before renaming/removing anything."); sb.AppendLine("- If unsure about a change's impact, ask the user first."); sb.AppendLine("- For large refactors, do them incrementally with build verification between steps."); sb.AppendLine("- Use git_tool action='diff' to review your changes before committing."); sb.AppendLine("\n## Lint & Format"); sb.AppendLine("After code changes, check for available linters:"); sb.AppendLine("- Python: ruff, black, flake8, pylint"); sb.AppendLine("- JavaScript: eslint, prettier"); sb.AppendLine("- C#: dotnet format"); sb.AppendLine("- C++: clang-format"); sb.AppendLine("Run the appropriate linter via process tool if detected by dev_env_detect."); if (!string.IsNullOrEmpty(workFolder)) sb.AppendLine($"\nCurrent work folder: {workFolder}"); sb.AppendLine($"File permission mode: {llm.FilePermission}"); // 폴더 데이터 활용 sb.AppendLine("\nFolder Data Usage = ACTIVE. Use folder_map and file_read to understand the codebase."); sb.AppendLine("Analyze project structure before making changes. Read relevant files to understand context."); // 프리셋 시스템 프롬프트 lock (_convLock) { if (_currentConversation?.SystemCommand is { Length: > 0 } sysCmd) sb.AppendLine("\n" + sysCmd); } // 프로젝트 문맥 파일 (AX.md) 주입 — Phase 27-C: @include 지시어 해석 포함 sb.Append(LoadProjectContext(workFolder)); // 프로젝트 규칙 (.ax/rules/) 자동 주입 sb.Append(BuildProjectRulesSection(workFolder)); // 에이전트 메모리 주입 sb.Append(BuildMemorySection(workFolder)); // 피드백 학습 컨텍스트 주입 sb.Append(BuildFeedbackContext()); // Phase 27-B: 현재 파일 경로 기반 자동 스킬 주입 sb.Append(BuildPathBasedSkillSection()); return sb.ToString(); } /// 프로젝트 규칙 (.ax/rules/)을 시스템 프롬프트 섹션으로 포맷합니다. private string BuildProjectRulesSection(string? workFolder) { if (string.IsNullOrEmpty(workFolder)) return ""; if (!Llm.EnableProjectRules) return ""; try { var rules = Services.Agent.ProjectRulesService.LoadRules(workFolder); if (rules.Count == 0) return ""; // 컨텍스트별 필터링: Cowork=document, Code=always (기본) var when = _activeTab == "Code" ? "always" : "always"; var filtered = Services.Agent.ProjectRulesService.FilterRules(rules, when); return Services.Agent.ProjectRulesService.FormatForSystemPrompt(filtered); } catch (Exception) { return ""; } } /// 에이전트 메모리를 시스템 프롬프트 섹션으로 포맷합니다. private string BuildMemorySection(string? workFolder) { if (!Llm.EnableAgentMemory) return ""; var memService = CurrentApp?.MemoryService; if (memService == null || memService.Count == 0) return ""; // 메모리를 로드 (작업 폴더 변경 시 재로드) memService.Load(workFolder ?? ""); var all = memService.All; if (all.Count == 0) return ""; var sb = new System.Text.StringBuilder(); sb.AppendLine("\n## 프로젝트 메모리 (이전 대화에서 학습한 내용)"); sb.AppendLine("아래는 이전 대화에서 학습한 규칙과 선호도입니다. 작업 시 참고하세요."); sb.AppendLine("새로운 규칙이나 선호도를 발견하면 memory 도구의 save 액션으로 저장하세요."); sb.AppendLine("사용자가 이전 학습 내용과 다른 지시를 하면 memory 도구의 delete 후 새로 save 하세요.\n"); foreach (var group in all.GroupBy(e => e.Type)) { var label = group.Key switch { "rule" => "프로젝트 규칙", "preference" => "사용자 선호", "fact" => "프로젝트 사실", "correction" => "이전 교정", _ => group.Key, }; sb.AppendLine($"[{label}]"); foreach (var e in group.OrderByDescending(e => e.UseCount).Take(15)) sb.AppendLine($"- {e.Content}"); sb.AppendLine(); } return sb.ToString(); } /// 워크플로우 시각화 설정이 켜져있으면 분석기 창을 열고 이벤트를 구독합니다. private void OpenWorkflowAnalyzerIfEnabled() { var llm = Llm; if (!llm.DevMode || !llm.WorkflowVisualizer) return; if (_analyzerWindow == null) { // 새로 생성 _analyzerWindow = new WorkflowAnalyzerWindow(); _analyzerWindow.Closed += (_, _) => _analyzerWindow = null; // 테마 리소스 전달 foreach (var dict in System.Windows.Application.Current.Resources.MergedDictionaries) _analyzerWindow.Resources.MergedDictionaries.Add(dict); _analyzerWindow.Show(); } else if (!_analyzerWindow.IsVisible) { // Hide()로 숨겨진 창 → 기존 내용 유지한 채 다시 표시 _analyzerWindow.Show(); _analyzerWindow.Activate(); } else { // 이미 보이는 상태 → 새 에이전트 실행을 위해 초기화 후 활성화 _analyzerWindow.Reset(); _analyzerWindow.Activate(); } // 타임라인 탭으로 전환 (새 실행 시작) _analyzerWindow.SwitchToTimelineTab(); // 이벤트 구독 (중복 방지) _agentLoop.EventOccurred -= _analyzerWindow.OnAgentEvent; _agentLoop.EventOccurred += _analyzerWindow.OnAgentEvent; } /// 워크플로우 분석기 버튼의 표시 상태를 갱신합니다. private void UpdateAnalyzerButtonVisibility() { var llm = Llm; BtnShowAnalyzer.Visibility = (llm.DevMode && llm.WorkflowVisualizer) ? Visibility.Visible : Visibility.Collapsed; } /// 워크플로우 분석기 창을 수동으로 열거나 포커스합니다 (하단 바 버튼). private void BtnShowAnalyzer_Click(object sender, MouseButtonEventArgs e) { if (_analyzerWindow == null) { _analyzerWindow = new WorkflowAnalyzerWindow(); _analyzerWindow.Closed += (_, _) => _analyzerWindow = null; foreach (var dict in System.Windows.Application.Current.Resources.MergedDictionaries) _analyzerWindow.Resources.MergedDictionaries.Add(dict); // 에이전트 이벤트 구독 _agentLoop.EventOccurred -= _analyzerWindow.OnAgentEvent; _agentLoop.EventOccurred += _analyzerWindow.OnAgentEvent; _analyzerWindow.Show(); } else if (!_analyzerWindow.IsVisible) { _analyzerWindow.Show(); _analyzerWindow.Activate(); } else { _analyzerWindow.Activate(); } } // ─── Phase 18-A: 백그라운드 에이전트 완료 알림 ────────────────────────── /// /// 위임 에이전트(delegate_agent)가 백그라운드에서 완료되면 트레이 알림을 표시합니다. /// private void OnBackgroundAgentCompleted(object? sender, AgentCompletedEventArgs e) { // UI 스레드에서 안전하게 실행 Dispatcher.BeginInvoke(() => { var task = e.Task; if (e.Error != null) { Services.NotificationService.Notify( $"에이전트 '{task.AgentType}' 실패", $"ID {task.Id}: {e.Error[..Math.Min(80, e.Error.Length)]}"); } else { Services.NotificationService.Notify( $"에이전트 '{task.AgentType}' 완료", $"ID {task.Id}: {task.Description[..Math.Min(60, task.Description.Length)]}"); } }); } /// 에이전트 루프 동안 누적 토큰 (하단 바 표시용) private int _agentCumulativeInputTokens; private int _agentCumulativeOutputTokens; private static readonly HashSet WriteToolNames = new(StringComparer.OrdinalIgnoreCase) { "file_write", "file_edit", "html_create", "xlsx_create", "docx_create", "csv_create", "md_create", "script_create", "diff_preview", "open_external", }; private void OnAgentEvent(AgentEvent evt) { // 에이전트 이벤트를 채팅 UI에 표시 (도구 호출/결과 배너) AddAgentEventBanner(evt); AutoScrollIfNeeded(); // 하단 상태바 업데이트 UpdateStatusBar(evt); // 하단 바 토큰 누적 업데이트 (에이전트 루프 전체 합계) if (evt.InputTokens > 0 || evt.OutputTokens > 0) { _agentCumulativeInputTokens += evt.InputTokens; _agentCumulativeOutputTokens += evt.OutputTokens; UpdateStatusTokens(_agentCumulativeInputTokens, _agentCumulativeOutputTokens); } // 스티키 진행률 바 업데이트 UpdateAgentProgressBar(evt); // 계획 뷰어 단계 갱신 if (evt.StepCurrent > 0 && evt.StepTotal > 0) UpdatePlanViewerStep(evt); if (evt.Type == AgentEventType.Complete) CompletePlanViewer(); // 파일 탐색기 자동 새로고침 if (evt.Success && !string.IsNullOrEmpty(evt.FilePath)) RefreshFileTreeIfVisible(); // suggest_actions 도구 결과 → 후속 작업 칩 표시 if (evt.Type == AgentEventType.ToolResult && evt.ToolName == "suggest_actions" && evt.Success) RenderSuggestActionChips(evt.Summary); // 파일 생성/수정 결과가 있으면 미리보기 자동 표시 또는 갱신 if (evt.Success && !string.IsNullOrEmpty(evt.FilePath) && (evt.Type == AgentEventType.ToolResult || evt.Type == AgentEventType.Complete) && WriteToolNames.Contains(evt.ToolName)) { var autoPreview = Llm.AutoPreview; if (autoPreview == "auto") { // 별도 창 미리보기: 이미 열린 파일이면 새로고침, 아니면 새 탭 추가 if (PreviewWindow.IsOpen) PreviewWindow.RefreshIfOpen(evt.FilePath); else TryShowPreview(evt.FilePath); // 새 파일이면 항상 표시 if (!PreviewWindow.IsOpen) TryShowPreview(evt.FilePath); } } } }