Compare commits
2 Commits
a3b3522bb7
...
4cbe60052e
| Author | SHA1 | Date | |
|---|---|---|---|
| 4cbe60052e | |||
| 6c5b0c5be3 |
10
README.md
10
README.md
@@ -7,6 +7,16 @@ Windows 전용 시맨틱 런처 & 워크스페이스 매니저
|
||||
개발 참고: Claw Code 동등성 작업 추적 문서
|
||||
`docs/claw-code-parity-plan.md`
|
||||
|
||||
- 업데이트: 2026-04-05 18:58 (KST)
|
||||
- AX Agent의 `의견 요청`/`질문` 흐름을 transcript 내 카드 우선 구조로 전환했습니다.
|
||||
- `user_ask` 도구는 별도 `UserAskDialog`를 먼저 띄우지 않고, 본문 안에서 선택지/직접 입력/전달로 응답을 완료합니다.
|
||||
- 계획 승인과 사용자 질문이 같은 transcript-first UX 원칙을 따르도록 정리했습니다.
|
||||
|
||||
- 업데이트: 2026-04-05 22:04 (KST)
|
||||
- `claw-code`와 AX Agent를 같은 기준으로 비교할 수 있도록 canonical prompt set 10종을 parity 문서에 고정했습니다. Chat 기본/장문, Cowork 문서/데이터, Code 수정/빌드, queue follow-up, post-compaction, permission 승인, slash skill 진입까지 핵심 회귀 흐름을 한 세트로 검증하도록 정리했습니다.
|
||||
- 도구/스킬 비교 기준도 parity 문서에 추가했습니다. AX는 문서/오피스/데이터/업무형 도구가 더 풍부하고, `claw-code`는 transcript-native tool/approval/permission 메시지 구조가 더 정교하다는 차이를 명시했습니다.
|
||||
- 계획 승인 UX는 `claw-code` 쪽 흐름에 더 가깝게 inline 우선으로 바꿨습니다. AX Agent는 이제 승인 요청 시 `PlanViewerWindow`를 먼저 띄우지 않고 transcript 내부의 승인/수정/취소 버튼을 우선 보여주며, 계획 상세는 하단 `계획` 버튼으로만 여는 보조 경로를 사용합니다.
|
||||
|
||||
- 업데이트: 2026-04-05 16:55 (KST)
|
||||
- `claw-code` 대비 AX Agent 추정 진척율 기준선을 문서에 남겼습니다. 현재 기준은 핵심 엔진 `82%`, 채팅 메인 UI `68%`, Cowork/Code 상태 UX `63%`, 내부 설정 연결 `88%`, 전체 AX Agent 동등 품질 `74%`입니다.
|
||||
- 메인 핵심 엔진 로직에 직접 영향을 주는 설정은 최소화 원칙으로 다시 검토하기 시작했습니다. 이미 실질 선택지가 사라진 `계획 모드` 계열은 사용자 노출을 더 줄였고, 남은 엔진성 설정은 개발자 탭 중심으로 계속 정리합니다.
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
# AX Copilot - 媛쒕컻 臾몄꽌
|
||||
|
||||
- Document update: 2026-04-05 18:58 (KST) - Switched `UserAskCallback` in `ChatWindow.xaml.cs` to a transcript-first inline card flow. `user_ask` no longer defaults to `UserAskDialog`; it renders an in-stream question card with choice pills, direct text input, and submit/cancel actions, then returns only the chosen answer to the engine.
|
||||
- Document update: 2026-04-05 18:58 (KST) - Aligned user-question UX with the same transcript-first principle already used for plan approval. `PlanViewerWindow` remains a secondary detail surface, while the primary approval/question decision now happens inside the AX Agent message timeline.
|
||||
|
||||
- Document update: 2026-04-05 16:55 (KST) - Recorded the current `claw-code` parity estimate for AX Agent: core execution engine `82%`, main chat UI `68%`, Cowork/Code status UX `63%`, internal settings linkage `88%`, overall AX Agent parity `74%`.
|
||||
- Document update: 2026-04-05 16:55 (KST) - Added an engine-settings review rule for ongoing cleanup: settings that materially alter the main execution route should be minimized, kept developer-only when necessary, or removed from user-facing surfaces when they no longer represent real runtime choices. Plan-mode remnants were reduced further as part of this pass.
|
||||
- Document update: 2026-04-05 16:55 (KST) - Simplified AX Agent message rows and sidebar conversation items toward the `claw-code` reading model. Message bubbles now use tighter padding/radius/meta text, and conversation rows now prefer lightweight running/failure summary text over heavier success/failure badge cards.
|
||||
@@ -4745,3 +4748,6 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎.
|
||||
- 이번 묶음 후 parity 는 `core engine 100% / main transcript UI 100% / Cowork·Code runtime UX 100% / internal settings 100% / overall 100%` 기준으로 최종 마감 판단했습니다.
|
||||
- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\` 경고 0 / 오류 0
|
||||
- 업데이트: 2026-04-05 21:43 (KST)
|
||||
- Document update: 2026-04-05 22:04 (KST) - Added a canonical 10-prompt regression set to `docs/claw-code-parity-plan.md` so AX Agent and `claw-code` can be compared on the same Chat/Cowork/Code scenarios: basic/long chat, document/data cowork, bug-fix/build code, queued follow-up, post-compaction continuity, permission approval, and slash skill entry.
|
||||
- Document update: 2026-04-05 22:04 (KST) - Added a tool/skill delta snapshot to the parity plan. AX remains stronger on document/office/data workflows, while `claw-code` remains stronger on transcript-native approval/tool-result/permission message taxonomy.
|
||||
- Document update: 2026-04-05 22:04 (KST) - Switched plan approval flow to transcript-first. `CreatePlanDecisionCallback()` now prepares `PlanViewerWindow` without auto-opening it, shows the inline approval controls in the transcript first, and keeps the bottom `계획` button as the secondary detail surface.
|
||||
|
||||
@@ -122,6 +122,91 @@
|
||||
- Manual scenario 3: Code task with execution log noise -> completion -> compact -> next turn -> reopen
|
||||
- Manual scenario 4: AX Agent internal settings change -> immediate runtime reflection without layout regression
|
||||
|
||||
## Canonical Prompt Set
|
||||
- Updated: 2026-04-05 22:04 (KST)
|
||||
- The following prompt set should be used for AX vs `claw-code` parity checks. The goal is not byte-identical output, but equivalent execution route, approval behavior, and artifact/result quality.
|
||||
|
||||
1. Chat basic answer
|
||||
- Prompt: `회의 일정 조정 메일을 정중한 한국어로 써줘`
|
||||
- Apply to: `Chat`
|
||||
- Verify: normal reply render, retry/regenerate stability, reopen durability
|
||||
|
||||
2. Chat long-form explanation
|
||||
- Prompt: `RAG와 fine-tuning 차이를 실무 관점으로 7가지로 설명해줘`
|
||||
- Apply to: `Chat`
|
||||
- Verify: long response rendering, compaction follow-up continuity
|
||||
|
||||
3. Cowork document task
|
||||
- Prompt: `신규 ERP 도입 제안서 초안을 작성해줘. 목적, 범위, 기대효과, 추진일정 포함`
|
||||
- Apply to: `Cowork`
|
||||
- Verify: topic/task preset routing, plan-first execution, actual document-oriented output path
|
||||
|
||||
4. Cowork data task
|
||||
- Prompt: `매출 CSV를 분석해서 월별 추세와 이상치를 요약해줘`
|
||||
- Apply to: `Cowork`
|
||||
- Verify: data-analysis tool choice, reduced runtime noise, final summary quality
|
||||
|
||||
5. Code bug-fix task
|
||||
- Prompt: `현재 프로젝트에서 설정 저장 버그 원인 찾고 수정해줘`
|
||||
- Apply to: `Code`
|
||||
- Verify: read/search/edit path, diff persistence, reopen consistency
|
||||
|
||||
6. Code build/test task
|
||||
- Prompt: `빌드 오류를 재현하고 수정한 뒤 다시 빌드해줘`
|
||||
- Apply to: `Code`
|
||||
- Verify: build/test loop, failure retry, final completion message
|
||||
|
||||
7. Queued follow-up
|
||||
- Prompt sequence:
|
||||
- `이 창 레이아웃 문제 원인 찾아줘`
|
||||
- `끝나면 README도 같이 갱신해줘`
|
||||
- Apply to: `Cowork`, `Code`
|
||||
- Verify: queue chaining, next-turn pickup without UI mutation
|
||||
|
||||
8. Post-compaction continuity
|
||||
- Prompt: `지금까지 논의한 내용을 5줄로 이어서 정리하고 다음 작업 제안해줘`
|
||||
- Apply to: `Chat`, `Cowork`, `Code`
|
||||
- Verify: compact-after-next-turn continuity, no token-only completion
|
||||
|
||||
9. Permission approval
|
||||
- Prompt: `이 파일을 수정해서 저장해줘`
|
||||
- Apply to: `Code`
|
||||
- Verify: permission request, approve/reject rendering, final transcript consistency
|
||||
|
||||
10. Slash / skill entry
|
||||
- Prompt: `/bug-hunt src 폴더 잠재 버그 찾아줘`
|
||||
- Apply to: `Code`
|
||||
- Verify: slash entry uses the same prepared-execution route as normal send
|
||||
|
||||
## Tool / Skill Delta Snapshot
|
||||
- Updated: 2026-04-05 22:04 (KST)
|
||||
- AX tool registry count is larger than `claw-code`, but the shape is different.
|
||||
- AX reference: `src/AxCopilot/Services/Agent/ToolRegistry.cs`
|
||||
- `claw-code` reference: `src/tools/*`, `src/skills/bundledSkills.ts`
|
||||
|
||||
### AX stronger areas
|
||||
- Document/office generation and conversion (`ExcelSkill`, `DocxSkill`, `PptxSkill`, `DocumentPlannerTool`, `DocumentAssemblerTool`)
|
||||
- Data/business utilities (`DataPivotTool`, `SqlTool`, `FormatConvertTool`, `TextSummarizeTool`)
|
||||
- WPF-integrated enterprise UX and Korean workflow presets
|
||||
|
||||
### claw-code stronger areas
|
||||
- Transcript-native tool use / rejection / approval message taxonomy
|
||||
- Plan approval request/response rendering in the message stream
|
||||
- Permission and tool-result message consistency
|
||||
- Bundled skill registry and skill message integration
|
||||
|
||||
### Remaining parity target
|
||||
- Keep AX's richer business/document tool set
|
||||
- Bring transcript rendering and approval/status UX closer to `claw-code`
|
||||
|
||||
## Transcript-First Approval / Ask UX
|
||||
- Updated: 2026-04-05 18:58 (KST)
|
||||
- `plan approval` and `user ask` should both resolve inside the transcript first.
|
||||
- Secondary windows are allowed only as detail surfaces, not as the primary decision flow.
|
||||
- AX implementation status:
|
||||
- `plan approval`: transcript-first, detail view via `PlanViewerWindow`
|
||||
- `user ask`: transcript-first inline question card with choices / direct input / submit
|
||||
|
||||
## Current Snapshot
|
||||
- Updated: 2026-04-05 19:42 (KST)
|
||||
- Estimated parity:
|
||||
|
||||
@@ -67,6 +67,7 @@ public partial class ChatWindow : Window
|
||||
private bool _aiIconPulseStopped; // 펄스 1회만 중지
|
||||
private WorkflowAnalyzerWindow? _analyzerWindow; // 워크플로우 분석기
|
||||
private PlanViewerWindow? _planViewerWindow; // 실행 계획 뷰어
|
||||
private Border? _userAskCard; // transcript 내 질문 카드
|
||||
private bool _userScrolled; // 사용자가 위로 스크롤했는지
|
||||
private readonly HashSet<string> _sessionPermissionRules = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<string, bool> _sessionMcpEnabledOverrides = new(StringComparer.OrdinalIgnoreCase);
|
||||
@@ -200,16 +201,7 @@ public partial class ChatWindow : Window
|
||||
|
||||
return decision != PermissionRequestWindow.PermissionPromptResult.Reject;
|
||||
},
|
||||
UserAskCallback = async (question, options, defaultValue) =>
|
||||
{
|
||||
string? response = null;
|
||||
var appDispatcher = System.Windows.Application.Current?.Dispatcher ?? Dispatcher;
|
||||
await appDispatcher.InvokeAsync(() =>
|
||||
{
|
||||
response = UserAskDialog.Show(question, options, defaultValue);
|
||||
});
|
||||
return response;
|
||||
},
|
||||
UserAskCallback = ShowInlineUserAskAsync,
|
||||
};
|
||||
SubAgentTool.StatusChanged += OnSubAgentStatusChanged;
|
||||
|
||||
@@ -9902,6 +9894,250 @@ public partial class ChatWindow : Window
|
||||
outerStack.Children.Add(resultLabel);
|
||||
}
|
||||
|
||||
private async Task<string?> ShowInlineUserAskAsync(string question, List<string> options, string defaultValue)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<string?>();
|
||||
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
AddUserAskCard(question, options, defaultValue, tcs);
|
||||
});
|
||||
|
||||
var completed = await Task.WhenAny(tcs.Task, Task.Delay(TimeSpan.FromMinutes(5)));
|
||||
if (completed != tcs.Task)
|
||||
{
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
RemoveUserAskCard();
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
return await tcs.Task;
|
||||
}
|
||||
|
||||
private void RemoveUserAskCard()
|
||||
{
|
||||
if (_userAskCard == null)
|
||||
return;
|
||||
|
||||
MessagePanel.Children.Remove(_userAskCard);
|
||||
_userAskCard = null;
|
||||
}
|
||||
|
||||
private void AddUserAskCard(string question, List<string> options, string defaultValue, TaskCompletionSource<string?> tcs)
|
||||
{
|
||||
RemoveUserAskCard();
|
||||
|
||||
var accentBrush = TryFindResource("AccentColor") as Brush ?? Brushes.CornflowerBlue;
|
||||
var accentColor = ((SolidColorBrush)accentBrush).Color;
|
||||
var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.White;
|
||||
var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
|
||||
var borderBrush = TryFindResource("BorderColor") as Brush
|
||||
?? new SolidColorBrush(Color.FromArgb(0x24, accentColor.R, accentColor.G, accentColor.B));
|
||||
var itemBg = TryFindResource("ItemBackground") as Brush
|
||||
?? new SolidColorBrush(Color.FromArgb(0x10, accentColor.R, accentColor.G, accentColor.B));
|
||||
var hoverBg = TryFindResource("ItemHoverBackground") as Brush
|
||||
?? new SolidColorBrush(Color.FromArgb(0x16, 0xFF, 0xFF, 0xFF));
|
||||
var okBrush = BrushFromHex("#10B981");
|
||||
var dangerBrush = BrushFromHex("#EF4444");
|
||||
|
||||
var container = new Border
|
||||
{
|
||||
Margin = new Thickness(40, 4, 90, 8),
|
||||
HorizontalAlignment = HorizontalAlignment.Left,
|
||||
MaxWidth = Math.Max(420, GetMessageMaxWidth() - 36),
|
||||
Background = itemBg,
|
||||
BorderBrush = borderBrush,
|
||||
BorderThickness = new Thickness(1),
|
||||
CornerRadius = new CornerRadius(14),
|
||||
Padding = new Thickness(14, 12, 14, 12),
|
||||
};
|
||||
|
||||
var outer = new StackPanel();
|
||||
outer.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "의견 요청",
|
||||
FontSize = 12.5,
|
||||
FontWeight = FontWeights.SemiBold,
|
||||
Foreground = primaryText,
|
||||
});
|
||||
outer.Children.Add(new TextBlock
|
||||
{
|
||||
Text = question,
|
||||
Margin = new Thickness(0, 4, 0, 10),
|
||||
FontSize = 12.5,
|
||||
Foreground = primaryText,
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
LineHeight = 20,
|
||||
});
|
||||
|
||||
Border? selectedOption = null;
|
||||
string selectedResponse = defaultValue;
|
||||
|
||||
if (options.Count > 0)
|
||||
{
|
||||
var optionPanel = new WrapPanel
|
||||
{
|
||||
Margin = new Thickness(0, 0, 0, 10),
|
||||
ItemWidth = double.NaN,
|
||||
};
|
||||
|
||||
foreach (var option in options.Where(static option => !string.IsNullOrWhiteSpace(option)))
|
||||
{
|
||||
var optionLabel = option.Trim();
|
||||
var optBorder = new Border
|
||||
{
|
||||
Background = Brushes.Transparent,
|
||||
BorderBrush = borderBrush,
|
||||
BorderThickness = new Thickness(1),
|
||||
CornerRadius = new CornerRadius(999),
|
||||
Padding = new Thickness(10, 6, 10, 6),
|
||||
Margin = new Thickness(0, 0, 8, 8),
|
||||
Cursor = Cursors.Hand,
|
||||
Child = new TextBlock
|
||||
{
|
||||
Text = optionLabel,
|
||||
FontSize = 12,
|
||||
Foreground = primaryText,
|
||||
},
|
||||
};
|
||||
|
||||
optBorder.MouseEnter += (s, _) =>
|
||||
{
|
||||
if (!ReferenceEquals(selectedOption, s))
|
||||
((Border)s).Background = hoverBg;
|
||||
};
|
||||
optBorder.MouseLeave += (s, _) =>
|
||||
{
|
||||
if (!ReferenceEquals(selectedOption, s))
|
||||
((Border)s).Background = Brushes.Transparent;
|
||||
};
|
||||
optBorder.MouseLeftButtonUp += (_, _) =>
|
||||
{
|
||||
if (selectedOption != null)
|
||||
{
|
||||
selectedOption.Background = Brushes.Transparent;
|
||||
selectedOption.BorderBrush = borderBrush;
|
||||
}
|
||||
|
||||
selectedOption = optBorder;
|
||||
selectedOption.Background = new SolidColorBrush(Color.FromArgb(0x18, accentColor.R, accentColor.G, accentColor.B));
|
||||
selectedOption.BorderBrush = accentBrush;
|
||||
selectedResponse = optionLabel;
|
||||
};
|
||||
|
||||
optionPanel.Children.Add(optBorder);
|
||||
}
|
||||
|
||||
outer.Children.Add(optionPanel);
|
||||
}
|
||||
|
||||
outer.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "직접 입력",
|
||||
FontSize = 11.5,
|
||||
Foreground = secondaryText,
|
||||
Margin = new Thickness(0, 0, 0, 6),
|
||||
});
|
||||
|
||||
var inputBox = new TextBox
|
||||
{
|
||||
Text = defaultValue,
|
||||
AcceptsReturn = true,
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
MinHeight = 42,
|
||||
MaxHeight = 100,
|
||||
FontSize = 12.5,
|
||||
Padding = new Thickness(10, 8, 10, 8),
|
||||
Background = Brushes.Transparent,
|
||||
Foreground = primaryText,
|
||||
CaretBrush = primaryText,
|
||||
BorderBrush = borderBrush,
|
||||
BorderThickness = new Thickness(1),
|
||||
};
|
||||
inputBox.TextChanged += (_, _) =>
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(inputBox.Text))
|
||||
{
|
||||
selectedResponse = inputBox.Text.Trim();
|
||||
if (selectedOption != null)
|
||||
{
|
||||
selectedOption.Background = Brushes.Transparent;
|
||||
selectedOption.BorderBrush = borderBrush;
|
||||
selectedOption = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
outer.Children.Add(inputBox);
|
||||
|
||||
var buttonRow = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
HorizontalAlignment = HorizontalAlignment.Right,
|
||||
Margin = new Thickness(0, 12, 0, 0),
|
||||
};
|
||||
|
||||
Border BuildActionButton(string label, Brush bg, Brush fg)
|
||||
{
|
||||
return new Border
|
||||
{
|
||||
Background = bg,
|
||||
BorderBrush = bg,
|
||||
BorderThickness = new Thickness(1),
|
||||
CornerRadius = new CornerRadius(999),
|
||||
Padding = new Thickness(12, 7, 12, 7),
|
||||
Margin = new Thickness(8, 0, 0, 0),
|
||||
Cursor = Cursors.Hand,
|
||||
Child = new TextBlock
|
||||
{
|
||||
Text = label,
|
||||
FontSize = 12,
|
||||
FontWeight = FontWeights.SemiBold,
|
||||
Foreground = fg,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
var cancelBtn = BuildActionButton("취소", Brushes.Transparent, dangerBrush);
|
||||
cancelBtn.BorderBrush = new SolidColorBrush(Color.FromArgb(0x40, 0xEF, 0x44, 0x44));
|
||||
cancelBtn.MouseLeftButtonUp += (_, _) =>
|
||||
{
|
||||
RemoveUserAskCard();
|
||||
tcs.TrySetResult(null);
|
||||
};
|
||||
buttonRow.Children.Add(cancelBtn);
|
||||
|
||||
var submitBtn = BuildActionButton("전달", okBrush, Brushes.White);
|
||||
submitBtn.MouseLeftButtonUp += (_, _) =>
|
||||
{
|
||||
var finalResponse = !string.IsNullOrWhiteSpace(inputBox.Text)
|
||||
? inputBox.Text.Trim()
|
||||
: selectedResponse?.Trim();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(finalResponse))
|
||||
finalResponse = defaultValue?.Trim();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(finalResponse))
|
||||
return;
|
||||
|
||||
RemoveUserAskCard();
|
||||
tcs.TrySetResult(finalResponse);
|
||||
};
|
||||
buttonRow.Children.Add(submitBtn);
|
||||
|
||||
outer.Children.Add(buttonRow);
|
||||
container.Child = outer;
|
||||
_userAskCard = container;
|
||||
|
||||
container.Opacity = 0;
|
||||
container.BeginAnimation(UIElement.OpacityProperty, new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(180)));
|
||||
MessagePanel.Children.Add(container);
|
||||
ForceScrollToEnd();
|
||||
inputBox.Focus();
|
||||
inputBox.CaretIndex = inputBox.Text.Length;
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// 실행 계획 뷰어 (PlanViewerWindow) 연동
|
||||
// ════════════════════════════════════════════════════════════
|
||||
@@ -9916,22 +10152,10 @@ public partial class ChatWindow : Window
|
||||
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
// PlanViewerWindow 생성 또는 재사용
|
||||
if (_planViewerWindow == null || !IsWindowAlive(_planViewerWindow))
|
||||
{
|
||||
_planViewerWindow = new PlanViewerWindow(this);
|
||||
_planViewerWindow.Closing += (_, e) =>
|
||||
{
|
||||
e.Cancel = true;
|
||||
_planViewerWindow.Hide();
|
||||
};
|
||||
}
|
||||
|
||||
// 계획 표시 + 승인 대기
|
||||
_planViewerWindow.ShowPlanAsync(planSummary, steps, tcs);
|
||||
|
||||
// 하단 바 계획 버튼 표시
|
||||
EnsurePlanViewerWindow();
|
||||
_planViewerWindow?.LoadPlan(planSummary, steps, tcs);
|
||||
ShowPlanButton(true);
|
||||
AddDecisionButtons(tcs, options);
|
||||
});
|
||||
|
||||
// 5분 타임아웃
|
||||
@@ -9973,6 +10197,19 @@ public partial class ChatWindow : Window
|
||||
};
|
||||
}
|
||||
|
||||
private void EnsurePlanViewerWindow()
|
||||
{
|
||||
if (_planViewerWindow != null && IsWindowAlive(_planViewerWindow))
|
||||
return;
|
||||
|
||||
_planViewerWindow = new PlanViewerWindow(this);
|
||||
_planViewerWindow.Closing += (_, e) =>
|
||||
{
|
||||
e.Cancel = true;
|
||||
_planViewerWindow.Hide();
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>하단 바에 계획 보기 버튼을 표시/숨김합니다.</summary>
|
||||
private void ShowPlanButton(bool show)
|
||||
{
|
||||
|
||||
@@ -287,7 +287,7 @@ internal sealed class PlanViewerWindow : Window
|
||||
// 공개 API
|
||||
// ════════════════════════════════════════════════════════════
|
||||
|
||||
public Task<string?> ShowPlanAsync(string planText, List<string> steps, TaskCompletionSource<string?> tcs)
|
||||
public void LoadPlan(string planText, List<string> steps, TaskCompletionSource<string?> tcs)
|
||||
{
|
||||
_planText = planText;
|
||||
_steps = steps;
|
||||
@@ -304,7 +304,11 @@ internal sealed class PlanViewerWindow : Window
|
||||
RenderSteps();
|
||||
BuildApprovalButtons();
|
||||
_statusBar.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
public Task<string?> ShowPlanAsync(string planText, List<string> steps, TaskCompletionSource<string?> tcs)
|
||||
{
|
||||
LoadPlan(planText, steps, tcs);
|
||||
Show();
|
||||
Activate();
|
||||
return tcs.Task;
|
||||
|
||||
Reference in New Issue
Block a user