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:
2026-04-05 13:58:16 +09:00
parent 5c142e1235
commit 35fbfc933d
4 changed files with 147 additions and 84 deletions

View File

@@ -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";
}
}