Compare commits
2 Commits
a635f24399
...
84e5cd9485
| Author | SHA1 | Date | |
|---|---|---|---|
| 84e5cd9485 | |||
| 0ef37554bc |
@@ -284,15 +284,15 @@
|
||||
| # | 기능 | 설명 | 우선순위 | 갭 |
|
||||
|---|------|------|----------|----|
|
||||
| 18-A1 | **코디네이터 에이전트 모드** | 계획·라우팅 전담, 구현은 서브에이전트 위임 | 최고 | G1 |
|
||||
| 18-A2 | **Worktree 격리 서브에이전트** | 서브에이전트별 독립 git working copy. 승인 후 병합 | 최고 | — |
|
||||
| 18-A3 | **에이전트 팀 위임 (delegate 도구)** | 특화 에이전트(코드·문서·보안·리서치) 독립 루프 실행 | 최고 | G1 |
|
||||
| 18-A4 | **백그라운드 에이전트 + 타입별 메모리** | 비동기 실행 + 트레이 알림. agent-memory/타입/MEMORY.md | 높음 | — |
|
||||
| 18-A2 ✅ | **Worktree 격리 서브에이전트** | WorktreeManager 구현 완료 + DelegateAgentTool에 주입 (isolation:"worktree" 파라미터 지원) | 최고 | — |
|
||||
| 18-A3 ✅ | **에이전트 팀 위임 (delegate 도구)** | DelegateAgentTool 구현 + ToolRegistry 등록 완료. BackgroundAgentService + WorktreeManager 통합 | 최고 | G1 |
|
||||
| 18-A4 ✅ | **백그라운드 에이전트 + 완료 알림** | BackgroundAgentService.AgentCompleted → ChatWindow 트레이 알림 연결 완료 | 높음 | — |
|
||||
|
||||
### Group B — 디버깅 + 생태계
|
||||
|
||||
| # | 기능 | 설명 | 우선순위 | 갭 |
|
||||
|---|------|------|----------|----|
|
||||
| 18-B1 | **에이전트 리플레이/디버깅** | Event-Sourced 로그. 과거 세션 재생·분기 재실행 | 높음 | G8 |
|
||||
| 18-B1 ✅ | **에이전트 리플레이/디버깅** | AgentReplayService + ReplayTimelineViewModel 구현. WorkflowAnalyzerWindow에 "리플레이" 탭 통합 (세션 선택·재생 컨트롤·이벤트 스트림) | 높음 | G8 |
|
||||
| 18-C1 | **플러그인 갤러리 + 레지스트리** | 로컬 NAS/Git 기반 인앱 갤러리. zip 설치 | 높음 | — |
|
||||
| 18-C2 | **AI 스니펫** | ;email {수신자} {주제} 등 LLM 초안 자동 생성 | 중간 | — |
|
||||
| 18-C3 | **파라미터 퀵링크** | jira {번호} → URL 변수 치환 | 중간 | — |
|
||||
|
||||
@@ -1968,18 +1968,18 @@ allowed-tools: process file_read grep_tool
|
||||
| 구분 | 내용 |
|
||||
|------|------|
|
||||
| **에이전트 채팅 화면 개편 (Phase 17-UI)** | 채팅 창 서브 바에 Plan 모드 3상태 토글 (Off / Auto / Always) 추가. 클릭 시 순환 전환, 활성 상태 색상 강조. `LlmSettings.PlanMode` 설정 연동 |
|
||||
| **Reflexion 강화 (Phase 17-A)** | `ReflexionService` 확장: 자기 평가 → 전략 수정 루프 구현. 도구 실행 실패 시 반성 기반 재시도 전략 자동 선택 |
|
||||
| **구조화된 이벤트 로그 (Phase 17-B)** | `AgentEventLog` JSONL 기록 시스템. `AgentLoopService`에 세션 ID 생성 + SessionStart / ToolRequest / ToolResult / SessionEnd 이벤트 기록 통합 |
|
||||
| **훅 시스템 고도화 (Phase 17-C)** | `ExtendedHookRunner`: PreCompact / UserPromptSubmit / AgentThinking 훅 이벤트 추가. `asyncRewake` 속성으로 비동기 훅 완료 후 에이전트 재개 가능 |
|
||||
| **스킬 시스템 고도화 (Phase 17-D)** | `PathBasedSkillActivator`: 경로 패턴 기반 스킬 자동 활성화. `SkillExtensionStore` 사이드카 패턴으로 스킬 확장 메타데이터 관리 |
|
||||
| **메모리/컨텍스트 고도화 (Phase 17-E)** | `ContextCondenser` 경로 규칙 연동. `PathScopedRuleInjector`로 작업 폴더에 따른 규칙 자동 주입. `@include` 지시문 처리 |
|
||||
| **권한 시스템 고도화 (Phase 17-F)** | `PermissionSystem` Chain of Responsibility 패턴. Allow/Deny 규칙 설정 UI 연동. acceptEdits 모드에서 파일 편집 도구 자동 승인 |
|
||||
| **멀티파일 Diff + 자동 컨텍스트 (Phase 17-G)** | `MultiFileDiffViewModel`: 파일별 diff 추적 + 일괄 수락. `AutoContextCollector`: `@파일명` 멘션 정규식 파싱 + 자동 파일 내용 주입. `ToolRiskMapper`: 도구별 Low/Medium/High 위험도 분류 |
|
||||
| **멀티에이전트 팀 (Phase 18-A)** | `DelegateAgentTool` + `BackgroundAgentService` + `WorktreeManager`. `AgentLoopService`에 `RunSubAgentAsync` 위임 연결. ToolRegistry에 자동 등록 |
|
||||
| **Reflexion 강화 (Phase 17-A)** | `ReflexionService` + `ReflexionRepository`: 성공·실패 시 자기평가 기록. 동일 유형 작업 시 과거 교훈 시스템 메시지 자동 주입. `AgentLoopService.Reflexion.cs` 파셜 추가 |
|
||||
| **TaskState Working Memory + 이벤트 로그 (Phase 17-B)** | `TaskStateService`: 세션 요약·참조 파일 목록 JSON 영속화. 압축 후에도 Working Memory 복원. `AgentEventLog` JSONL 기록 (SessionStart/UserMessage/ToolRequest/ToolResult/AssistantMessage/HookFired/CompactionTriggered 등). `AgentLoopService.TaskState.cs` 파셜 추가 |
|
||||
| **훅 시스템 고도화 (Phase 17-C)** | `ExtendedHookRunner` — Command/Http/Prompt 3종 타입. `HookEventKind` 열거형 18종 이벤트 (SessionStart/SessionEnd/UserPromptSubmit/PreToolUse/PostToolUse/PostToolUseFailure/PreCompact/PostCompact/AgentStop 등). Prompt 모드: `{{tool_name}}` `{{tool_input}}` 변수 치환 → LLM 보안 검사 → Block 신호 탐지. `AgentLoopService.ExtendedHooks.cs` 파셜 추가 |
|
||||
| **스킬 시스템 고도화 (Phase 17-D)** | `PathBasedSkillActivator`: `paths:` 프론트매터 glob 매칭으로 파일 작업 시 스킬 자동 컨텍스트 주입. `context:fork` 스킬: `SkillManagerTool.SetForkRunner()` DI 콜백 주입 → 격리된 LLM 호출. `AgentLoopService.Skills.cs` 파셜 추가 |
|
||||
| **계층 메모리/컨텍스트 고도화 (Phase 17-E)** | `HierarchicalMemoryService`: Managed→User→Project→Local 4-layer AX.md 수집. `AxMdIncludeResolver`: `@파일경로` 재귀 해석 최대 5단계 (순환 참조 감지). `PathScopedRuleInjector`: `.ax/rules/*.md` paths: 프론트매터 기반 경로 범위 규칙 주입. `AgentLoopService.Memory.cs` 파셜 추가. 설정: `EnableMemorySystem` |
|
||||
| **권한 시스템 고도화 (Phase 17-F)** | `PermissionDecisionService` Chain of Responsibility: DenyRuleHandler→AllowRuleHandler→AcceptEditsHandler→PlanModeHandler→BypassHandler. `AgentLoopService.Permissions.cs` 파셜 추가. `EvaluateToolPermission()`: Deny=즉시차단/Allow=확인 스킵/Ask=기존 흐름. `PermissionsConfig.AllowRules/DenyRules` 설정 기반 패턴 규칙 |
|
||||
| **멀티파일 Diff 추적 + 도구 위험도 (Phase 17-G)** | `AgentLoopService.DiffTracker.cs` 파셜: `CaptureOriginalFileContent()` 쓰기 전 원본 캡처, `TrackFileModificationForDiff()` 성공 후 `MultiFileDiffViewModel` 기록. `ToolRiskMapper` 연계 ToolCall 이벤트에 `[⚠ 높음]`/`[• 보통]` 위험도 태그 추가. 설정: `EnableDiffTracker` |
|
||||
| **멀티에이전트 팀 (Phase 18-A)** | `DelegateAgentTool` + `AgentLoopService.RunSubAgentAsync()` 위임 연결. ToolRegistry에 자동 등록. 서브에이전트 독립 루프 실행 지원 |
|
||||
| **에이전트 리플레이 (Phase 18-B)** | `AgentReplayService`: JSONL 이벤트 로그 파일 열거 + 단계별 재생. `ReplayTimelineViewModel`: 재생 UI 바인딩 ViewModel |
|
||||
| **플러그인 설치 서비스 (Phase 18-C)** | `PluginInstallService`: 로컬 zip 파일 기반 설치 (URL 다운로드 없음). `PluginManifest` / `InstalledPlugin` / `PluginInstallResult` 레코드. 레지스트리 JSONL 영속화 |
|
||||
| **사내/사외 네트워크 모드** | `AppSettings.InternalModeEnabled` (기본: 사내 모드). 사외 전환 시 비밀번호 `axgo123!` 인증 필요. `WebSearchHandler` + `HttpTool` 에서 모드 체크 적용 |
|
||||
| **알림 센터 (L3-7)** | `NotificationCenterService`: 알림 타입(Info/Success/Warning/Error) + 히스토리 큐(최대 50건). `NotificationRaised` 이벤트로 UI 구독 지원. 기존 `NotificationService` 트레이 BalloonTip 위임 |
|
||||
| **알림 센터 (L3-7)** | `NotificationCenterService`: 알림 타입(Info/Success/Warning/Error) + 히스토리 큐(최대 50건). `NotificationRaised` 이벤트로 UI 구독 지원 |
|
||||
| **플러그인 갤러리 ViewModel (L3-1)** | `PluginGalleryViewModel`: 설치된 플러그인 목록 + 설치/제거/활성화 커맨드 바인딩. `PluginItemViewModel` 래퍼 |
|
||||
| **파라미터 퀵링크 (L3-4)** | `QuickLinkHandler` (`ql` 프리픽스) + `UrlTemplateEngine`. `{0}`, `{query}` 플레이스홀더 치환. 설정에서 `QuickLinkEntry` 목록 관리 |
|
||||
| **AI 스니펫 (L3-3)** | `AiSnippetHandler` (`ai` 프리픽스) + `SnippetTemplateService`. 업무 이메일/요약/번역/코드리뷰/커밋 기본 템플릿 5종 내장. LLM 호출 후 클립보드 복사. AI 비활성화 시 항목 미표시 |
|
||||
|
||||
@@ -905,6 +905,32 @@ Ollama의 모든 모델이 도구 사용을 지원하지는 않습니다. 아래
|
||||
<!-- 푸터 -->
|
||||
<!-- ══════════════════════════════════════════════════════════ -->
|
||||
|
||||
<!-- v1.8.0 -->
|
||||
<div class="version-entry" style="border-left:3px solid #6366F1; background:linear-gradient(90deg,#6366F110,transparent);">
|
||||
<div class="version-meta">
|
||||
<div class="version-badge" style="background:#6366F1; color:#fff;">v1.8.0</div>
|
||||
</div>
|
||||
<div class="version-changes">
|
||||
<ul>
|
||||
<li><strong>에이전트 인프라 전면 고도화</strong> — Claude Code 아키텍처 기반 Phase 17 전 구간(A~G) 완료</li>
|
||||
<li><strong>자기성찰 메모리</strong> — 성공·실패 경험을 기록하고 동일 유형 작업 시 자동 참고합니다</li>
|
||||
<li><strong>세션 상태 영속화</strong> — 대화 압축 후에도 작업 상태와 참조 파일 목록이 유지됩니다</li>
|
||||
<li><strong>훅 시스템 고도화</strong> — 18종 라이프사이클 이벤트. AI 보안 검사(Prompt 훅)로 위험 명령 차단 가능</li>
|
||||
<li><strong>스킬 fork 실행</strong> — 스킬을 격리된 AI 컨텍스트에서 실행하여 메인 대화 오염 방지</li>
|
||||
<li><strong>경로 기반 스킬/규칙 자동 주입</strong> — 작업 파일에 따라 관련 스킬과 코딩 규칙이 자동 적용됩니다</li>
|
||||
<li><strong>계층 메모리 통합</strong> — 전사·개인·프로젝트·로컬 4단계 AX.md를 병합하여 컨텍스트로 자동 주입</li>
|
||||
<li><strong>권한 시스템 고도화</strong> — acceptEdits 모드: 파일 편집 자동 승인. 패턴 기반 허용/차단 규칙 지원</li>
|
||||
<li><strong>멀티파일 Diff 추적</strong> — 에이전트가 수정한 파일 변경 내역을 세션 단위로 추적·표시</li>
|
||||
<li><strong>도구 위험도 표시</strong> — 실행 중인 도구의 위험도(높음/보통)를 실시간으로 표시합니다</li>
|
||||
<li><strong>사내/사외 네트워크 모드</strong> — 기본 사내 모드에서 외부 인터넷 접속 차단. 사외 모드 전환 시 인증 필요</li>
|
||||
<li><strong>멀티에이전트 팀</strong> — 코드·문서·보안·리서치 특화 서브에이전트에 작업 위임 가능</li>
|
||||
<li><strong>AI 스니펫</strong> — 업무 이메일·요약·번역·코드리뷰 등 자주 쓰는 AI 작업을 단축 명령으로 즉시 실행</li>
|
||||
<li><strong>파라미터 퀵링크</strong> — 자주 여는 URL에 변수를 넣어 검색어·번호 등을 바로 전달</li>
|
||||
<li><strong>알림 센터</strong> — 에이전트·시스템 알림을 타입별(정보/성공/경고/오류)로 분류·관리</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- v1.7.2 -->
|
||||
<div class="version-entry" style="border-left:3px solid #22C55E; background:linear-gradient(90deg,#22C55E10,transparent);">
|
||||
<div class="version-meta">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
@@ -344,7 +344,9 @@ td {
|
||||
<li><a href="#sec-symbol" style="color:#fff;text-decoration:none;font-weight:600;">⚡ 특수 기호 예약어</a></li>
|
||||
<li><a href="#sec-keyword" style="color:#fff;text-decoration:none;font-weight:600;">📝 영문 키워드 예약어</a></li>
|
||||
<li><a href="#sec-tips" style="color:#fff;text-decoration:none;font-weight:600;">💡 유용한 팁</a></li>
|
||||
<li><a href="#sec-models" style="color:#fff;text-decoration:none;font-weight:600;">🧠 AI 모델 호환성</a></li>
|
||||
<li><a href="#sec-settings" style="color:#fff;text-decoration:none;font-weight:600;">⚙️ 설정 기능 탭</a></li>
|
||||
<li><a href="#sec-version" style="color:#fff;text-decoration:none;font-weight:600;">📋 버전 이력</a></li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
@@ -737,6 +739,90 @@ td {
|
||||
<span class="kw">info disk</span>에서 드라이브 항목을 Enter하면 탐색기로 바로 열립니다.
|
||||
</div>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════ -->
|
||||
<!-- AI 모델 호환성 -->
|
||||
<!-- ══════════════════════════════════════════════════════════ -->
|
||||
<h2 id="sec-models" class="section-title">🧠 AI 모델 호환성 — 도구 사용(Function Calling) 지원 현황</h2>
|
||||
<p class="section-desc">
|
||||
AX Agent의 Cowork/Code 탭은 <strong>도구 사용(Function Calling)</strong>을 통해 파일 읽기/쓰기, 문서 생성, 코드 실행 등을 수행합니다.<br>
|
||||
도구 사용을 지원하지 않는 모델에서는 Cowork/Code 기능이 정상 동작하지 않으며, Chat 탭의 일반 대화만 가능합니다.
|
||||
</p>
|
||||
|
||||
<h3 style="font-size:16px;font-weight:700;margin:20px 0 10px;color:#1e293b;">☁️ 클라우드 서비스 (모두 도구 사용 지원)</h3>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead><tr>
|
||||
<th class="hd-dark" style="width:120px">서비스</th>
|
||||
<th class="hd-dark">모델</th>
|
||||
<th class="hd-dark" style="width:100px">도구 사용</th>
|
||||
<th class="hd-dark" style="width:100px">컨텍스트</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<tr><td>Claude</td><td>claude-sonnet-4, claude-opus-4</td><td style="color:#16a34a;font-weight:bold;">✅ 지원</td><td>200K</td></tr>
|
||||
<tr><td>Claude</td><td>claude-haiku-3.5</td><td style="color:#16a34a;font-weight:bold;">✅ 지원</td><td>200K</td></tr>
|
||||
<tr><td>Gemini</td><td>gemini-2.5-pro, gemini-2.5-flash</td><td style="color:#16a34a;font-weight:bold;">✅ 지원</td><td>1M</td></tr>
|
||||
<tr><td>Gemini</td><td>gemini-2.0-flash</td><td style="color:#16a34a;font-weight:bold;">✅ 지원</td><td>1M</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h3 style="font-size:16px;font-weight:700;margin:20px 0 10px;color:#1e293b;">🏠 Ollama 로컬 모델 — 도구 사용 지원 여부</h3>
|
||||
<p style="font-size:13px;color:#64748b;margin-bottom:10px;">
|
||||
Ollama의 모든 모델이 도구 사용을 지원하지는 않습니다. 아래 표에서 ✅ 표시된 모델만 Cowork/Code 탭에서 사용 가능합니다.
|
||||
</p>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead><tr>
|
||||
<th class="hd-dark" style="width:180px">모델</th>
|
||||
<th class="hd-dark" style="width:100px">도구 사용</th>
|
||||
<th class="hd-dark" style="width:100px">파라미터</th>
|
||||
<th class="hd-dark">비고</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<tr style="background:#f0fdf4;"><td><strong>qwen2.5</strong></td><td style="color:#16a34a;font-weight:bold;">✅ 지원</td><td>7B ~ 72B</td><td>⭐ <strong>가장 추천</strong> — 한국어 + 도구 사용 안정적</td></tr>
|
||||
<tr style="background:#f0fdf4;"><td><strong>qwen3</strong></td><td style="color:#16a34a;font-weight:bold;">✅ 지원</td><td>8B ~ 32B</td><td>최신 모델, 도구 지원 우수</td></tr>
|
||||
<tr style="background:#f0fdf4;"><td><strong>qwen2.5-coder</strong></td><td style="color:#16a34a;font-weight:bold;">✅ 지원</td><td>7B ~ 32B</td><td>⭐ Code 탭 추천 — 코드 특화 + 도구</td></tr>
|
||||
<tr><td>llama3.1</td><td style="color:#16a34a;font-weight:bold;">✅ 지원</td><td>8B / 70B</td><td>Meta 공식 도구 지원</td></tr>
|
||||
<tr><td>llama3.3</td><td style="color:#16a34a;font-weight:bold;">✅ 지원</td><td>70B</td><td>도구 호출 개선 버전</td></tr>
|
||||
<tr><td>mistral</td><td style="color:#16a34a;font-weight:bold;">✅ 지원</td><td>7B</td><td>경량, 기본적인 도구 지원</td></tr>
|
||||
<tr><td>mistral-nemo</td><td style="color:#16a34a;font-weight:bold;">✅ 지원</td><td>12B</td><td>도구 지원 개선</td></tr>
|
||||
<tr><td>command-r</td><td style="color:#16a34a;font-weight:bold;">✅ 지원</td><td>35B</td><td>Cohere, 도구 특화</td></tr>
|
||||
<tr><td>firefunction-v2</td><td style="color:#16a34a;font-weight:bold;">✅ 지원</td><td>70B</td><td>Function Calling 특화</td></tr>
|
||||
<tr><td>granite3</td><td style="color:#16a34a;font-weight:bold;">✅ 지원</td><td>2B / 8B</td><td>IBM, 경량 도구 지원</td></tr>
|
||||
<tr style="background:#fef2f2;"><td>deepseek-r1</td><td style="color:#dc2626;font-weight:bold;">❌ 미지원</td><td>다양</td><td>추론 특화, 도구 호출 불가</td></tr>
|
||||
<tr style="background:#fef2f2;"><td>phi3 / phi4</td><td style="color:#dc2626;font-weight:bold;">❌ 미지원</td><td>3B ~ 14B</td><td>도구 미지원</td></tr>
|
||||
<tr style="background:#fef2f2;"><td>gemma2</td><td style="color:#dc2626;font-weight:bold;">❌ 미지원</td><td>9B / 27B</td><td>도구 미지원</td></tr>
|
||||
<tr style="background:#fef2f2;"><td>codellama</td><td style="color:#dc2626;font-weight:bold;">❌ 미지원</td><td>7B ~ 70B</td><td>코드 특화이나 도구 미지원</td></tr>
|
||||
<tr style="background:#fef2f2;"><td>llava / bakllava</td><td style="color:#dc2626;font-weight:bold;">❌ 미지원</td><td>다양</td><td>비전 특화</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h3 style="font-size:16px;font-weight:700;margin:20px 0 10px;color:#1e293b;">💡 사내 환경 추천 조합</h3>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead><tr>
|
||||
<th class="hd-dark" style="width:150px">용도</th>
|
||||
<th class="hd-dark" style="width:180px">추천 모델</th>
|
||||
<th class="hd-dark" style="width:100px">VRAM</th>
|
||||
<th class="hd-dark">설명</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<tr><td>Cowork (문서 작성)</td><td><strong>qwen2.5:32b</strong> 이상</td><td>24GB+</td><td>한국어 품질과 도구 사용 모두 우수</td></tr>
|
||||
<tr><td>Code (코드 작성)</td><td><strong>qwen2.5-coder:32b</strong></td><td>24GB+</td><td>코드 특화 + 도구 지원</td></tr>
|
||||
<tr><td>경량 (GPU 부족)</td><td><strong>qwen2.5:7b</strong> 또는 <strong>granite3:8b</strong></td><td>8GB</td><td>기본적인 도구 사용 가능</td></tr>
|
||||
<tr><td>최고 품질 (로컬)</td><td><strong>qwen3:32b</strong> 또는 <strong>llama3.3:70b</strong></td><td>24~48GB+</td><td>대규모 문서/코드 작업에 적합</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="info-box" style="background:linear-gradient(135deg,#1e1e3f 0%,#2d1b69 100%); color:#e2e8f0; border:1px solid #4338ca;">
|
||||
<strong style="color:#fbbf24;">⚠️ 도구 미지원 모델 사용 시</strong><br>
|
||||
도구 사용을 지원하지 않는 모델을 Cowork/Code 탭에서 사용하면 <code style="background:#4338ca55;padding:2px 6px;border-radius:4px;">400 BadRequest</code> 에러가 발생합니다.<br>
|
||||
이 경우 AX Copilot이 자동으로 일반 텍스트 응답으로 전환하지만, 파일 생성·코드 실행 등의 에이전트 기능은 사용할 수 없습니다.<br>
|
||||
<span style="color:#60a5fa;">Chat 탭의 일반 대화는 모든 모델에서 정상 동작합니다.</span>
|
||||
</div>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════ -->
|
||||
<!-- 6. 설정 기능 토글 -->
|
||||
<!-- ══════════════════════════════════════════════════════════ -->
|
||||
@@ -815,6 +901,11 @@ td {
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════ -->
|
||||
<!-- 푸터 -->
|
||||
<!-- ══════════════════════════════════════════════════════════ -->
|
||||
|
||||
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════ -->
|
||||
<!-- 푸터 -->
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,3 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
@@ -70,7 +71,11 @@ public class SkillManagerTool : IAgentTool
|
||||
|
||||
private static ToolResult ListSkills()
|
||||
{
|
||||
var skills = SkillService.Skills;
|
||||
// Phase 17-D3: UserInvocable=false 스킬은 내부 전용이므로 목록에서 제외
|
||||
var skills = SkillService.Skills
|
||||
.Where(s => s.IsUserInvocable())
|
||||
.ToList();
|
||||
|
||||
if (skills.Count == 0)
|
||||
return ToolResult.Ok("로드된 스킬이 없습니다. %APPDATA%\\AxCopilot\\skills\\에 *.skill.md 파일을 추가하세요.");
|
||||
|
||||
|
||||
@@ -11,6 +11,9 @@ public class ToolRegistry : IDisposable
|
||||
/// <summary>등록된 모든 도구 목록.</summary>
|
||||
public IReadOnlyCollection<IAgentTool> All => _tools.Values;
|
||||
|
||||
/// <summary>Phase 18-A: 백그라운드 에이전트 서비스 (AgentCompleted 이벤트 구독용).</summary>
|
||||
public BackgroundAgentService? BackgroundAgentService { get; private set; }
|
||||
|
||||
/// <summary>도구를 이름으로 찾습니다.</summary>
|
||||
public IAgentTool? Get(string name) =>
|
||||
_tools.TryGetValue(name, out var tool) ? tool : null;
|
||||
@@ -161,7 +164,9 @@ public class ToolRegistry : IDisposable
|
||||
// Phase 18-A: 위임 에이전트 (멀티에이전트 오케스트레이션)
|
||||
var memoryRepo = new AgentTypeMemoryRepository();
|
||||
var bgService = new BackgroundAgentService(memoryRepo);
|
||||
registry.Register(new DelegateAgentTool(bgService, memoryRepo));
|
||||
var worktreeMgr = new WorktreeManager(); // Phase 18-A2: worktree 격리
|
||||
registry.BackgroundAgentService = bgService; // ChatWindow에서 AgentCompleted 구독
|
||||
registry.Register(new DelegateAgentTool(bgService, memoryRepo, worktreeMgr));
|
||||
|
||||
return registry;
|
||||
}
|
||||
|
||||
@@ -407,6 +407,32 @@ public partial class ChatWindow
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Phase 18-A: 백그라운드 에이전트 완료 알림 ──────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// 위임 에이전트(delegate_agent)가 백그라운드에서 완료되면 트레이 알림을 표시합니다.
|
||||
/// </summary>
|
||||
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)]}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>에이전트 루프 동안 누적 토큰 (하단 바 표시용)</summary>
|
||||
private int _agentCumulativeInputTokens;
|
||||
private int _agentCumulativeOutputTokens;
|
||||
|
||||
@@ -104,6 +104,10 @@ public partial class ChatWindow : Window
|
||||
},
|
||||
};
|
||||
|
||||
// Phase 18-A: 백그라운드 에이전트 완료 알림 구독
|
||||
if (_toolRegistry.BackgroundAgentService != null)
|
||||
_toolRegistry.BackgroundAgentService.AgentCompleted += OnBackgroundAgentCompleted;
|
||||
|
||||
// 설정에서 초기값 로드 (Loaded 전에도 null 방지)
|
||||
_selectedMood = settings.Settings.Llm.DefaultMood ?? "modern";
|
||||
_folderDataUsage = settings.Settings.Llm.FolderDataUsage ?? "active";
|
||||
|
||||
@@ -114,6 +114,16 @@
|
||||
Foreground="{DynamicResource PrimaryText}" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<!-- Phase 18-B: 리플레이 탭 -->
|
||||
<Border x:Name="TabReplay" CornerRadius="6" Padding="12,6" Margin="0,0,4,0"
|
||||
Cursor="Hand" MouseLeftButtonUp="TabReplay_Click">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock x:Name="TabReplayIcon" Text="" FontFamily="Segoe MDL2 Assets" FontSize="11"
|
||||
Foreground="{DynamicResource PrimaryText}" VerticalAlignment="Center" Margin="0,0,6,0"/>
|
||||
<TextBlock x:Name="TabReplayText" Text="리플레이" FontSize="12" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource PrimaryText}" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
@@ -153,6 +163,55 @@
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
<!-- ═══ 콘텐츠: 리플레이 (Phase 18-B) ═══ -->
|
||||
<ScrollViewer Grid.Row="3" x:Name="ReplayScroller" VerticalScrollBarVisibility="Auto"
|
||||
Margin="12,0,12,0" Padding="0,0,4,0" Visibility="Collapsed">
|
||||
<StackPanel x:Name="ReplayPanel" Margin="0,4,0,8">
|
||||
<!-- 세션 선택 -->
|
||||
<TextBlock Text="📂 저장된 세션" FontSize="13" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource PrimaryText}" Margin="4,0,0,6"/>
|
||||
<Border Background="{DynamicResource ItemBackground}" CornerRadius="8"
|
||||
Padding="8,6" Margin="0,0,0,8" MaxHeight="120">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel x:Name="ReplaySessionList"/>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
<!-- 재생 컨트롤 -->
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,8">
|
||||
<Border x:Name="BtnReplayStart" CornerRadius="6" Padding="12,6" Cursor="Hand"
|
||||
Background="{DynamicResource AccentColor}" Margin="0,0,6,0"
|
||||
MouseLeftButtonUp="BtnReplayStart_Click">
|
||||
<TextBlock Text="▶ 재생" FontSize="12" Foreground="White" FontWeight="SemiBold"/>
|
||||
</Border>
|
||||
<Border x:Name="BtnReplayStop" CornerRadius="6" Padding="12,6" Cursor="Hand"
|
||||
Background="{DynamicResource ItemBackground}" Margin="0,0,6,0"
|
||||
MouseLeftButtonUp="BtnReplayStop_Click">
|
||||
<TextBlock Text="■ 중단" FontSize="12" Foreground="{DynamicResource PrimaryText}" FontWeight="SemiBold"/>
|
||||
</Border>
|
||||
<TextBlock Text="속도 (ms):" FontSize="11" Foreground="{DynamicResource SecondaryText}"
|
||||
VerticalAlignment="Center" Margin="8,0,6,0"/>
|
||||
<Slider x:Name="ReplaySpeedSlider" Minimum="50" Maximum="1000" Value="200"
|
||||
Width="80" VerticalAlignment="Center" ValueChanged="ReplaySpeedSlider_Changed"/>
|
||||
<TextBlock x:Name="ReplaySpeedLabel" Text="200ms" FontSize="11"
|
||||
Foreground="{DynamicResource SecondaryText}" VerticalAlignment="Center" Margin="4,0,0,0"/>
|
||||
</StackPanel>
|
||||
<!-- 진행 표시줄 -->
|
||||
<ProgressBar x:Name="ReplayProgress" Height="4"
|
||||
Foreground="{DynamicResource AccentColor}"
|
||||
Background="{DynamicResource ItemBackground}"
|
||||
Margin="0,0,0,8" Minimum="0" Maximum="100" Value="0"/>
|
||||
<!-- 재생 이벤트 스트림 -->
|
||||
<TextBlock Text="▶ 이벤트 스트림" FontSize="13" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource PrimaryText}" Margin="4,0,0,6"/>
|
||||
<Border Background="{DynamicResource ItemBackground}" CornerRadius="8"
|
||||
Padding="8,6" Margin="0,0,0,4" MinHeight="60">
|
||||
<ScrollViewer x:Name="ReplayEventScroll" VerticalScrollBarVisibility="Auto" MaxHeight="200">
|
||||
<StackPanel x:Name="ReplayEventPanel"/>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
<!-- ═══ 상세 패널 ═══ -->
|
||||
<Border Grid.Row="4" x:Name="DetailPanel" Visibility="Collapsed"
|
||||
Background="{DynamicResource ItemBackground}" CornerRadius="8"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
@@ -6,6 +7,7 @@ using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
using AxCopilot.Services.Agent;
|
||||
using AxCopilot.ViewModels;
|
||||
|
||||
namespace AxCopilot.Views;
|
||||
|
||||
@@ -35,6 +37,9 @@ public partial class WorkflowAnalyzerWindow : Window
|
||||
private readonly List<(int Iteration, int Input, int Output)> _tokenTrend = new();
|
||||
private long _lastEventMs; // 워터폴 시작 시간 기준
|
||||
|
||||
// Phase 18-B: 리플레이 ViewModel
|
||||
private readonly ReplayTimelineViewModel _replayVm = new();
|
||||
|
||||
// ─── 상하좌우 리사이즈 (WindowStyle=None 대응) ─────────────
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
|
||||
@@ -177,6 +182,14 @@ public partial class WorkflowAnalyzerWindow : Window
|
||||
RenderBottleneckCharts();
|
||||
}
|
||||
|
||||
// Phase 18-B: 리플레이 탭 클릭
|
||||
private void TabReplay_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
_activeTab = "replay";
|
||||
UpdateTabVisuals();
|
||||
LoadReplaySessions();
|
||||
}
|
||||
|
||||
private void UpdateTabVisuals()
|
||||
{
|
||||
var accentBrush = TryFindResource("AccentColor") as Brush ?? Brushes.CornflowerBlue;
|
||||
@@ -184,18 +197,25 @@ public partial class WorkflowAnalyzerWindow : Window
|
||||
?? new SolidColorBrush(Color.FromArgb(0x15, 0xFF, 0xFF, 0xFF));
|
||||
var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.White;
|
||||
|
||||
var isTimeline = _activeTab == "timeline";
|
||||
TabTimeline.Background = isTimeline ? accentBrush : itemBg;
|
||||
TabBottleneck.Background = !isTimeline ? accentBrush : itemBg;
|
||||
var isTimeline = _activeTab == "timeline";
|
||||
var isBottleneck = _activeTab == "bottleneck";
|
||||
var isReplay = _activeTab == "replay";
|
||||
|
||||
// 활성 탭: 흰색 텍스트 (AccentColor 배경 위 가시성), 비활성: PrimaryText
|
||||
TabTimelineIcon.Foreground = isTimeline ? Brushes.White : primaryText;
|
||||
TabTimelineText.Foreground = isTimeline ? Brushes.White : primaryText;
|
||||
TabBottleneckIcon.Foreground = !isTimeline ? Brushes.White : primaryText;
|
||||
TabBottleneckText.Foreground = !isTimeline ? Brushes.White : primaryText;
|
||||
TabTimeline.Background = isTimeline ? accentBrush : itemBg;
|
||||
TabBottleneck.Background = isBottleneck ? accentBrush : itemBg;
|
||||
TabReplay.Background = isReplay ? accentBrush : itemBg;
|
||||
|
||||
TimelineScroller.Visibility = isTimeline ? Visibility.Visible : Visibility.Collapsed;
|
||||
BottleneckScroller.Visibility = !isTimeline ? Visibility.Visible : Visibility.Collapsed;
|
||||
// 활성 탭: 흰색 텍스트, 비활성: PrimaryText
|
||||
TabTimelineIcon.Foreground = isTimeline ? Brushes.White : primaryText;
|
||||
TabTimelineText.Foreground = isTimeline ? Brushes.White : primaryText;
|
||||
TabBottleneckIcon.Foreground = isBottleneck ? Brushes.White : primaryText;
|
||||
TabBottleneckText.Foreground = isBottleneck ? Brushes.White : primaryText;
|
||||
TabReplayIcon.Foreground = isReplay ? Brushes.White : primaryText;
|
||||
TabReplayText.Foreground = isReplay ? Brushes.White : primaryText;
|
||||
|
||||
TimelineScroller.Visibility = isTimeline ? Visibility.Visible : Visibility.Collapsed;
|
||||
BottleneckScroller.Visibility = isBottleneck ? Visibility.Visible : Visibility.Collapsed;
|
||||
ReplayScroller.Visibility = isReplay ? Visibility.Visible : Visibility.Collapsed;
|
||||
DetailPanel.Visibility = isTimeline && DetailPanel.Tag != null
|
||||
? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
@@ -271,4 +291,163 @@ public partial class WorkflowAnalyzerWindow : Window
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Phase 18-B: 리플레이 탭 ──────────────────────────────────────────
|
||||
|
||||
private ReplaySessionInfo? _selectedReplaySession;
|
||||
|
||||
/// <summary>저장된 세션 목록을 리플레이 패널에 로드합니다.</summary>
|
||||
private void LoadReplaySessions()
|
||||
{
|
||||
_replayVm.LoadSessions();
|
||||
ReplaySessionList.Children.Clear();
|
||||
|
||||
var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.White;
|
||||
var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.LightGray;
|
||||
var itemHover = TryFindResource("ItemHoverBackground") as Brush
|
||||
?? new SolidColorBrush(Color.FromArgb(0x18, 0xFF, 0xFF, 0xFF));
|
||||
|
||||
if (_replayVm.Sessions.Count == 0)
|
||||
{
|
||||
ReplaySessionList.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "저장된 세션이 없습니다.",
|
||||
FontSize = 11,
|
||||
Foreground = secondaryText,
|
||||
Margin = new Thickness(4, 2, 0, 2),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var session in _replayVm.Sessions)
|
||||
{
|
||||
var row = new Border
|
||||
{
|
||||
CornerRadius = new CornerRadius(4),
|
||||
Padding = new Thickness(8, 4, 8, 4),
|
||||
Margin = new Thickness(0, 1, 0, 1),
|
||||
Cursor = System.Windows.Input.Cursors.Hand,
|
||||
Tag = session,
|
||||
};
|
||||
row.Child = new TextBlock
|
||||
{
|
||||
Text = $"{session.CreatedAt:MM-dd HH:mm} [{session.SessionId[..Math.Min(8, session.SessionId.Length)]}] {session.FileSizeBytes / 1024}KB",
|
||||
FontSize = 11,
|
||||
Foreground = primaryText,
|
||||
};
|
||||
row.MouseEnter += (_, _) => row.Background = itemHover;
|
||||
row.MouseLeave += (_, _) => row.Background = null;
|
||||
row.MouseLeftButtonUp += (_, _) =>
|
||||
{
|
||||
_selectedReplaySession = session;
|
||||
_replayVm.SelectedSession = session;
|
||||
// 선택 강조
|
||||
foreach (Border child in ReplaySessionList.Children.OfType<Border>())
|
||||
child.BorderThickness = new Thickness(0);
|
||||
row.BorderBrush = TryFindResource("AccentColor") as Brush;
|
||||
row.BorderThickness = new Thickness(1);
|
||||
};
|
||||
ReplaySessionList.Children.Add(row);
|
||||
}
|
||||
}
|
||||
|
||||
private void BtnReplayStart_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||
{
|
||||
if (_replayVm.SelectedSession == null) return;
|
||||
|
||||
ReplayEventPanel.Children.Clear();
|
||||
ReplayProgress.Value = 0;
|
||||
|
||||
// ReplayEventReceived 이벤트 구독 (중복 방지)
|
||||
_replayVm.ReplayEventReceived -= OnReplayEventReceived;
|
||||
_replayVm.ReplayEventReceived += OnReplayEventReceived;
|
||||
|
||||
_ = _replayVm.StartReplayAsync();
|
||||
}
|
||||
|
||||
private void OnReplayEventReceived(ReplayEventItem item)
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
var node = BuildReplayEventRow(item);
|
||||
ReplayEventPanel.Children.Add(node);
|
||||
ReplayEventScroll.ScrollToEnd();
|
||||
|
||||
// 진행 표시
|
||||
if (_replayVm.ProgressMax > 0)
|
||||
ReplayProgress.Value = (double)_replayVm.ProgressValue / _replayVm.ProgressMax * 100;
|
||||
});
|
||||
}
|
||||
|
||||
private void BtnReplayStop_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||
=> _replayVm.StopReplay();
|
||||
|
||||
private void ReplaySpeedSlider_Changed(object sender, System.Windows.RoutedPropertyChangedEventArgs<double> e)
|
||||
{
|
||||
_replayVm.ReplaySpeedMs = (int)e.NewValue;
|
||||
if (ReplaySpeedLabel != null)
|
||||
ReplaySpeedLabel.Text = $"{(int)e.NewValue}ms";
|
||||
}
|
||||
|
||||
/// <summary>리플레이 이벤트 하나를 작은 행 UI로 빌드합니다.</summary>
|
||||
private Border BuildReplayEventRow(ReplayEventItem item)
|
||||
{
|
||||
var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.White;
|
||||
var color = item.Record.Type switch
|
||||
{
|
||||
AgentEventLogType.ToolRequest => Color.FromRgb(0x60, 0xA5, 0xFA),
|
||||
AgentEventLogType.ToolResult => Color.FromRgb(0x34, 0xD3, 0x99),
|
||||
AgentEventLogType.Error => Color.FromRgb(0xF8, 0x71, 0x71),
|
||||
AgentEventLogType.SessionEnd => Color.FromRgb(0x34, 0xD3, 0x99),
|
||||
AgentEventLogType.AssistantMessage => Color.FromRgb(0xFB, 0xBF, 0x24),
|
||||
_ => Color.FromRgb(0x94, 0xA3, 0xB8),
|
||||
};
|
||||
var icon = item.Icon;
|
||||
var summary = item.Record.Payload ?? item.TypeLabel;
|
||||
|
||||
var grid = new System.Windows.Controls.Grid();
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(20) });
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(8, GridUnitType.Star) });
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
|
||||
|
||||
var iconTb = new TextBlock
|
||||
{
|
||||
Text = icon,
|
||||
FontFamily = ThemeResourceHelper.SegoeMdl2,
|
||||
FontSize = 10,
|
||||
Foreground = new SolidColorBrush(color),
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
};
|
||||
Grid.SetColumn(iconTb, 0);
|
||||
grid.Children.Add(iconTb);
|
||||
|
||||
var summaryTb = new TextBlock
|
||||
{
|
||||
Text = summary.Length > 60 ? summary[..57] + "…" : summary,
|
||||
FontSize = 10.5,
|
||||
Foreground = primaryText,
|
||||
TextTrimming = TextTrimming.CharacterEllipsis,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Margin = new Thickness(4, 0, 4, 0),
|
||||
};
|
||||
Grid.SetColumn(summaryTb, 1);
|
||||
grid.Children.Add(summaryTb);
|
||||
|
||||
var timeTb = new TextBlock
|
||||
{
|
||||
Text = item.Record.Timestamp.ToString("HH:mm:ss"),
|
||||
FontSize = 9,
|
||||
Foreground = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
};
|
||||
Grid.SetColumn(timeTb, 2);
|
||||
grid.Children.Add(timeTb);
|
||||
|
||||
return new Border
|
||||
{
|
||||
Child = grid,
|
||||
Padding = new Thickness(4, 2, 4, 2),
|
||||
Margin = new Thickness(0, 1, 0, 1),
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user