AX Agent 코워크·코드 흐름과 컨텍스트 관리를 claude-code 기준으로 대폭 정리
- 코워크·코드 프롬프트, 도구 선택, 문서 생성/검증 흐름을 claude-code 동등 품질 기준으로 재정렬함 - OpenAI/vLLM 경로의 오래된 tool history를 평탄화하고 최근 이력만 구조화해 컨텍스트 직렬화를 경량화함 - AX Agent UI를 테마 기준으로 재구성하고 플랜 승인/오버레이/이벤트 렌더링/명령 입력 상호작용을 개선함 - 파일 후보 제안, 반복 경로 정체 복구, LSP 보강, 문서·PPT 처리 개선, 설정/서비스 인터페이스 정리를 함께 반영함 - README.md 및 docs/DEVELOPMENT.md를 작업 시점별로 갱신함 - 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\ (경고 0, 오류 0)
This commit is contained in:
@@ -14,8 +14,21 @@ public partial class ChatWindow
|
||||
{
|
||||
return async (planSummary, options) =>
|
||||
{
|
||||
// 도구 실행 승인(확인/건너뛰기/취소)은 간결한 별도 다이얼로그로 처리
|
||||
var isToolApproval = options.Contains("확인") && !options.Contains("승인");
|
||||
if (isToolApproval)
|
||||
{
|
||||
string? toolResult = null;
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
toolResult = ToolApprovalWindow.Show(this, planSummary, options);
|
||||
});
|
||||
return toolResult;
|
||||
}
|
||||
|
||||
// 계획 승인은 PlanViewerV2로 처리
|
||||
var tcs = new TaskCompletionSource<string?>();
|
||||
var steps = TaskDecomposer.ExtractSteps(planSummary);
|
||||
var steps = ExtractPlanSteps(planSummary);
|
||||
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
@@ -26,10 +39,10 @@ public partial class ChatWindow
|
||||
ShowPlanButton(true);
|
||||
AddDecisionButtons(tcs, options);
|
||||
// 플랜 창 자동 표시 (사용자가 직접 BtnPlanViewer 클릭 안 해도 됨)
|
||||
if (_planViewerWindow != null && IsWindowAlive(_planViewerWindow))
|
||||
if (_planViewerWindow != null && IsPlanWindowAlive())
|
||||
{
|
||||
_planViewerWindow.Show();
|
||||
_planViewerWindow.Activate();
|
||||
PlanWindow?.Show();
|
||||
PlanWindow?.Activate();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -38,7 +51,7 @@ public partial class ChatWindow
|
||||
{
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
_planViewerWindow?.Hide();
|
||||
PlanWindow?.Hide();
|
||||
ResetPendingPlanPresentation();
|
||||
});
|
||||
return "취소";
|
||||
@@ -59,21 +72,12 @@ public partial class ChatWindow
|
||||
agentDecision = $"수정 요청: {result.Trim()}";
|
||||
}
|
||||
|
||||
if (result == null)
|
||||
// 승인/취소/수정 모두 계획 창 닫고 상태 초기화
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
_planViewerWindow?.SwitchToExecutionMode();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
_planViewerWindow?.Hide();
|
||||
ResetPendingPlanPresentation();
|
||||
});
|
||||
}
|
||||
PlanWindow?.Hide();
|
||||
ResetPendingPlanPresentation();
|
||||
});
|
||||
|
||||
return agentDecision;
|
||||
};
|
||||
@@ -81,17 +85,27 @@ public partial class ChatWindow
|
||||
|
||||
private void EnsurePlanViewerWindow()
|
||||
{
|
||||
if (_planViewerWindow != null && IsWindowAlive(_planViewerWindow))
|
||||
if (_planViewerWindow != null && IsPlanWindowAlive())
|
||||
return;
|
||||
|
||||
_planViewerWindow = new PlanViewerWindow(this);
|
||||
_planViewerWindow.Closing += (_, e) =>
|
||||
if (_settings.Settings.Llm.EnableNewPlanViewer)
|
||||
{
|
||||
e.Cancel = true;
|
||||
_planViewerWindow.Hide();
|
||||
};
|
||||
var v2 = new PlanViewerWindowV2(this);
|
||||
v2.Closing += (_, e) => { e.Cancel = true; v2.Hide(); };
|
||||
_planViewerWindow = v2;
|
||||
}
|
||||
else
|
||||
{
|
||||
var v1 = new PlanViewerWindow(this);
|
||||
v1.Closing += (_, e) => { e.Cancel = true; v1.Hide(); };
|
||||
_planViewerWindow = v1;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsPlanWindowAlive() => IsWindowAlive(_planViewerWindow as Window);
|
||||
|
||||
private Window? PlanWindow => _planViewerWindow as Window;
|
||||
|
||||
private void ShowPlanButton(bool show)
|
||||
{
|
||||
// 레거시: 이전 세션에서 동적 주입된 MoodIconPanel 칩 정리
|
||||
@@ -127,7 +141,7 @@ public partial class ChatWindow
|
||||
return;
|
||||
|
||||
EnsurePlanViewerWindow();
|
||||
if (_planViewerWindow != null && IsWindowAlive(_planViewerWindow))
|
||||
if (_planViewerWindow != null && IsPlanWindowAlive())
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_planViewerWindow.PlanText)
|
||||
|| _planViewerWindow.PlanText != (_pendingPlanSummary ?? string.Empty)
|
||||
@@ -135,8 +149,8 @@ public partial class ChatWindow
|
||||
{
|
||||
_planViewerWindow.LoadPlanPreview(_pendingPlanSummary ?? "", _pendingPlanSteps);
|
||||
}
|
||||
_planViewerWindow.Show();
|
||||
_planViewerWindow.Activate();
|
||||
PlanWindow?.Show();
|
||||
PlanWindow?.Activate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,7 +167,7 @@ public partial class ChatWindow
|
||||
|
||||
private void UpdatePlanViewerStep(AgentEvent evt)
|
||||
{
|
||||
if (_planViewerWindow == null || !IsWindowAlive(_planViewerWindow))
|
||||
if (_planViewerWindow == null || !IsPlanWindowAlive())
|
||||
return;
|
||||
|
||||
if (evt.StepCurrent > 0)
|
||||
@@ -162,7 +176,7 @@ public partial class ChatWindow
|
||||
|
||||
private void CompletePlanViewer()
|
||||
{
|
||||
if (_planViewerWindow != null && IsWindowAlive(_planViewerWindow))
|
||||
if (_planViewerWindow != null && IsPlanWindowAlive())
|
||||
_planViewerWindow.MarkComplete();
|
||||
ResetPendingPlanPresentation();
|
||||
}
|
||||
@@ -174,6 +188,49 @@ public partial class ChatWindow
|
||||
ShowPlanButton(false);
|
||||
}
|
||||
|
||||
/// <summary>document_plan 결과 텍스트에서 계획 단계(섹션 목록)를 추출합니다.</summary>
|
||||
private static List<string> ExtractPlanSteps(string planText)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(planText))
|
||||
return new List<string> { "문서 계획 검토" };
|
||||
|
||||
// 1) 번호 매긴 단계 (기존 TaskDecomposer)
|
||||
var numbered = TaskDecomposer.ExtractSteps(planText);
|
||||
if (numbered.Count >= 2) return numbered;
|
||||
|
||||
var sections = new List<string>();
|
||||
|
||||
// 2) JSON "heading" 필드 추출 (ExecuteSinglePassWithData 경로)
|
||||
var headingMatches = System.Text.RegularExpressions.Regex.Matches(
|
||||
planText, @"""heading""\s*:\s*""([^""]+)""");
|
||||
foreach (System.Text.RegularExpressions.Match m in headingMatches)
|
||||
sections.Add(m.Groups[1].Value);
|
||||
if (sections.Count >= 2) return sections;
|
||||
|
||||
// 3) HTML <h2> 태그 (ExecuteWithHtmlScaffold 경로)
|
||||
sections.Clear();
|
||||
var h2Matches = System.Text.RegularExpressions.Regex.Matches(
|
||||
planText, @"<h2>([^<]+)</h2>");
|
||||
foreach (System.Text.RegularExpressions.Match m in h2Matches)
|
||||
sections.Add(m.Groups[1].Value);
|
||||
if (sections.Count >= 2) return sections;
|
||||
|
||||
// 4) Markdown ## 헤딩
|
||||
sections.Clear();
|
||||
var mdMatches = System.Text.RegularExpressions.Regex.Matches(
|
||||
planText, @"(?:^|\n)##\s+(.+?)(?:\n|$)");
|
||||
foreach (System.Text.RegularExpressions.Match m in mdMatches)
|
||||
{
|
||||
var heading = m.Groups[1].Value.Trim();
|
||||
if (!heading.StartsWith("[") && !heading.Contains("즉시 실행"))
|
||||
sections.Add(heading);
|
||||
}
|
||||
if (sections.Count >= 2) return sections;
|
||||
|
||||
// 5) 폴백
|
||||
return new List<string> { "문서 계획 검토" };
|
||||
}
|
||||
|
||||
private static bool IsWindowAlive(Window? w)
|
||||
{
|
||||
if (w == null)
|
||||
|
||||
Reference in New Issue
Block a user