AX Agent: 실행 이벤트 세션 변이 경로 엔진으로 통합
- ChatWindow에 중복돼 있던 실행 이벤트/Agent run 교차 탭 복원 로직을 AxAgentExecutionEngine helper로 이동함 - AppendExecutionEvent, AppendAgentRun이 공통 session mutation 경로를 사용하도록 정리해 이후 runtime state 공통화 기반을 마련함 - README와 DEVELOPMENT 문서에 2026-04-05 15:42 (KST) 기준 변경 이력 반영 - 검증: 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:
@@ -16,6 +16,9 @@ public sealed class AxAgentExecutionEngine
|
||||
ExecutionMode Mode,
|
||||
IReadOnlyList<string> PromptStack,
|
||||
List<ChatMessage> Messages);
|
||||
public sealed record SessionMutationResult(
|
||||
ChatConversation CurrentConversation,
|
||||
ChatConversation UpdatedConversation);
|
||||
|
||||
public IReadOnlyList<string> BuildPromptStack(
|
||||
string? conversationSystem,
|
||||
@@ -159,6 +162,42 @@ public sealed class AxAgentExecutionEngine
|
||||
return normalized;
|
||||
}
|
||||
|
||||
public SessionMutationResult AppendExecutionEvent(
|
||||
ChatSessionStateService session,
|
||||
ChatStorageService storage,
|
||||
ChatConversation? activeConversation,
|
||||
string activeTab,
|
||||
string targetTab,
|
||||
AgentEvent evt)
|
||||
{
|
||||
return ApplyConversationMutation(
|
||||
session,
|
||||
storage,
|
||||
activeConversation,
|
||||
activeTab,
|
||||
targetTab,
|
||||
normalizedTarget => session.AppendExecutionEvent(normalizedTarget, evt, null));
|
||||
}
|
||||
|
||||
public SessionMutationResult AppendAgentRun(
|
||||
ChatSessionStateService session,
|
||||
ChatStorageService storage,
|
||||
ChatConversation? activeConversation,
|
||||
string activeTab,
|
||||
string targetTab,
|
||||
AgentEvent evt,
|
||||
string status,
|
||||
string summary)
|
||||
{
|
||||
return ApplyConversationMutation(
|
||||
session,
|
||||
storage,
|
||||
activeConversation,
|
||||
activeTab,
|
||||
targetTab,
|
||||
normalizedTarget => session.AppendAgentRun(normalizedTarget, evt, status, summary, null));
|
||||
}
|
||||
|
||||
public string NormalizeAssistantContent(
|
||||
ChatConversation conversation,
|
||||
string runTab,
|
||||
@@ -208,4 +247,86 @@ public sealed class AxAgentExecutionEngine
|
||||
FileName = source.FileName,
|
||||
};
|
||||
}
|
||||
|
||||
private static SessionMutationResult ApplyConversationMutation(
|
||||
ChatSessionStateService session,
|
||||
ChatStorageService storage,
|
||||
ChatConversation? activeConversation,
|
||||
string activeTab,
|
||||
string targetTab,
|
||||
Func<string, ChatConversation> mutate)
|
||||
{
|
||||
var normalizedTarget = NormalizeTabName(targetTab);
|
||||
var normalizedActive = NormalizeTabName(activeTab);
|
||||
ChatConversation updatedConversation;
|
||||
|
||||
if (string.Equals(normalizedTarget, normalizedActive, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
session.CurrentConversation = updatedConversation = mutate(normalizedTarget);
|
||||
return new SessionMutationResult(updatedConversation, updatedConversation);
|
||||
}
|
||||
|
||||
var activeSnapshot = activeConversation;
|
||||
var previousSessionConversation = session.CurrentConversation;
|
||||
updatedConversation = mutate(normalizedTarget);
|
||||
|
||||
if (activeSnapshot != null
|
||||
&& string.Equals(NormalizeTabName(activeSnapshot.Tab), normalizedActive, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
session.CurrentConversation = activeSnapshot;
|
||||
return new SessionMutationResult(activeSnapshot, updatedConversation);
|
||||
}
|
||||
|
||||
if (previousSessionConversation != null
|
||||
&& string.Equals(NormalizeTabName(previousSessionConversation.Tab), normalizedActive, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
session.CurrentConversation = previousSessionConversation;
|
||||
return new SessionMutationResult(previousSessionConversation, updatedConversation);
|
||||
}
|
||||
|
||||
var activeId = session.GetConversationId(normalizedActive);
|
||||
var restoredConversation = string.IsNullOrWhiteSpace(activeId)
|
||||
? null
|
||||
: storage.Load(activeId);
|
||||
|
||||
if (restoredConversation != null)
|
||||
{
|
||||
session.CurrentConversation = restoredConversation;
|
||||
return new SessionMutationResult(restoredConversation, updatedConversation);
|
||||
}
|
||||
|
||||
var fallbackConversation = session.LoadOrCreateConversation(normalizedActive, storage, GetFallbackSettings());
|
||||
session.CurrentConversation = fallbackConversation;
|
||||
return new SessionMutationResult(fallbackConversation, updatedConversation);
|
||||
}
|
||||
|
||||
private static SettingsService GetFallbackSettings()
|
||||
{
|
||||
return (System.Windows.Application.Current as App)?.SettingsService
|
||||
?? new SettingsService();
|
||||
}
|
||||
|
||||
private static string NormalizeTabName(string? tab)
|
||||
{
|
||||
var normalized = (tab ?? "").Trim();
|
||||
if (string.IsNullOrEmpty(normalized))
|
||||
return "Chat";
|
||||
|
||||
if (normalized.Contains("코워크", StringComparison.OrdinalIgnoreCase))
|
||||
return "Cowork";
|
||||
|
||||
var canonical = new string(normalized
|
||||
.Where(char.IsLetterOrDigit)
|
||||
.ToArray())
|
||||
.ToLowerInvariant();
|
||||
|
||||
if (canonical is "cowork" or "coworkcode" or "coworkcodetab")
|
||||
return "Cowork";
|
||||
|
||||
if (normalized.Contains("코드", StringComparison.OrdinalIgnoreCase)
|
||||
|| canonical is "code" or "codetab")
|
||||
return "Code";
|
||||
|
||||
return "Chat";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9166,48 +9166,17 @@ public partial class ChatWindow : Window
|
||||
if (session == null)
|
||||
return;
|
||||
|
||||
var normalizedTarget = NormalizeTabName(targetTab);
|
||||
var normalizedActive = NormalizeTabName(_activeTab);
|
||||
ChatConversation updatedConversation;
|
||||
if (string.Equals(normalizedTarget, normalizedActive, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_currentConversation = updatedConversation = session.AppendAgentRun(normalizedTarget, evt, status, summary, null);
|
||||
ScheduleConversationPersist(updatedConversation);
|
||||
return;
|
||||
}
|
||||
|
||||
var activeSnapshot = _currentConversation;
|
||||
var previousSessionConversation = session.CurrentConversation;
|
||||
updatedConversation = session.AppendAgentRun(normalizedTarget, evt, status, summary, null);
|
||||
ScheduleConversationPersist(updatedConversation);
|
||||
|
||||
if (activeSnapshot != null && string.Equals(NormalizeTabName(activeSnapshot.Tab), normalizedActive, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
session.CurrentConversation = activeSnapshot;
|
||||
_currentConversation = activeSnapshot;
|
||||
}
|
||||
else if (previousSessionConversation != null
|
||||
&& string.Equals(NormalizeTabName(previousSessionConversation.Tab), normalizedActive, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
session.CurrentConversation = previousSessionConversation;
|
||||
_currentConversation = previousSessionConversation;
|
||||
}
|
||||
else
|
||||
{
|
||||
var activeId = session.GetConversationId(normalizedActive);
|
||||
var activeConv = string.IsNullOrWhiteSpace(activeId) ? null : _storage.Load(activeId);
|
||||
if (activeConv != null)
|
||||
{
|
||||
session.CurrentConversation = activeConv;
|
||||
_currentConversation = activeConv;
|
||||
}
|
||||
else
|
||||
{
|
||||
var fallback = session.LoadOrCreateConversation(normalizedActive, _storage, _settings);
|
||||
session.CurrentConversation = fallback;
|
||||
_currentConversation = fallback;
|
||||
}
|
||||
}
|
||||
var result = _chatEngine.AppendAgentRun(
|
||||
session,
|
||||
_storage,
|
||||
_currentConversation,
|
||||
_activeTab,
|
||||
targetTab,
|
||||
evt,
|
||||
status,
|
||||
summary);
|
||||
_currentConversation = result.CurrentConversation;
|
||||
ScheduleConversationPersist(result.UpdatedConversation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9219,48 +9188,15 @@ public partial class ChatWindow : Window
|
||||
if (session == null)
|
||||
return;
|
||||
|
||||
var normalizedTarget = NormalizeTabName(targetTab);
|
||||
var normalizedActive = NormalizeTabName(_activeTab);
|
||||
ChatConversation updatedConversation;
|
||||
if (string.Equals(normalizedTarget, normalizedActive, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_currentConversation = updatedConversation = session.AppendExecutionEvent(normalizedTarget, evt, null);
|
||||
ScheduleConversationPersist(updatedConversation);
|
||||
return;
|
||||
}
|
||||
|
||||
var activeSnapshot = _currentConversation;
|
||||
var previousSessionConversation = session.CurrentConversation;
|
||||
updatedConversation = session.AppendExecutionEvent(normalizedTarget, evt, null);
|
||||
ScheduleConversationPersist(updatedConversation);
|
||||
|
||||
if (activeSnapshot != null && string.Equals(NormalizeTabName(activeSnapshot.Tab), normalizedActive, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
session.CurrentConversation = activeSnapshot;
|
||||
_currentConversation = activeSnapshot;
|
||||
}
|
||||
else if (previousSessionConversation != null
|
||||
&& string.Equals(NormalizeTabName(previousSessionConversation.Tab), normalizedActive, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
session.CurrentConversation = previousSessionConversation;
|
||||
_currentConversation = previousSessionConversation;
|
||||
}
|
||||
else
|
||||
{
|
||||
var activeId = session.GetConversationId(normalizedActive);
|
||||
var activeConv = string.IsNullOrWhiteSpace(activeId) ? null : _storage.Load(activeId);
|
||||
if (activeConv != null)
|
||||
{
|
||||
session.CurrentConversation = activeConv;
|
||||
_currentConversation = activeConv;
|
||||
}
|
||||
else
|
||||
{
|
||||
var fallback = session.LoadOrCreateConversation(normalizedActive, _storage, _settings);
|
||||
session.CurrentConversation = fallback;
|
||||
_currentConversation = fallback;
|
||||
}
|
||||
}
|
||||
var result = _chatEngine.AppendExecutionEvent(
|
||||
session,
|
||||
_storage,
|
||||
_currentConversation,
|
||||
_activeTab,
|
||||
targetTab,
|
||||
evt);
|
||||
_currentConversation = result.CurrentConversation;
|
||||
ScheduleConversationPersist(result.UpdatedConversation);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user