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:
@@ -7,6 +7,10 @@ Windows 전용 시맨틱 런처 & 워크스페이스 매니저
|
|||||||
개발 참고: Claw Code 동등성 작업 추적 문서
|
개발 참고: Claw Code 동등성 작업 추적 문서
|
||||||
`docs/claw-code-parity-plan.md`
|
`docs/claw-code-parity-plan.md`
|
||||||
|
|
||||||
|
- 업데이트: 2026-04-05 15:42 (KST)
|
||||||
|
- AX Agent 엔진 공통화 1차로, Cowork/Code 실행 이벤트와 Agent run 기록을 탭별 현재 대화에 누적한 뒤 원래 활성 탭 대화를 복원하는 로직을 `ChatWindow`에서 `AxAgentExecutionEngine` helper로 옮겼습니다.
|
||||||
|
- 이제 실행 이벤트/최근 run 기록 반영 시 창 코드가 직접 교차 탭 복원 경로를 중복 처리하지 않고, 엔진의 공통 세션 mutation 경로를 사용합니다.
|
||||||
|
|
||||||
- 업데이트: 2026-04-05 15:34 (KST)
|
- 업데이트: 2026-04-05 15:34 (KST)
|
||||||
- AX Agent 개선 계획 기준을 이전 AX 비교본이 아니라 실제 `claw-code` 런타임 축으로 다시 고정했습니다. 현재 참조 spine은 `bootstrap/state.ts -> bridge/initReplBridge.ts -> bridge/sessionRunner.ts -> screens/REPL.tsx -> components/Messages.tsx -> components/StatusLine.tsx` 입니다.
|
- AX Agent 개선 계획 기준을 이전 AX 비교본이 아니라 실제 `claw-code` 런타임 축으로 다시 고정했습니다. 현재 참조 spine은 `bootstrap/state.ts -> bridge/initReplBridge.ts -> bridge/sessionRunner.ts -> screens/REPL.tsx -> components/Messages.tsx -> components/StatusLine.tsx` 입니다.
|
||||||
- 이에 맞춰 AX Agent 개선도 `상태 정규화 -> 실행 준비 공통화 -> AgentLoop 이벤트 정규화 -> 타임라인 렌더 일원화 -> 컴포저/상태바 단순화 -> 복구/재개 검증` 순서로 진행하도록 parity 문서를 갱신했습니다.
|
- 이에 맞춰 AX Agent 개선도 `상태 정규화 -> 실행 준비 공통화 -> AgentLoop 이벤트 정규화 -> 타임라인 렌더 일원화 -> 컴포저/상태바 단순화 -> 복구/재개 검증` 순서로 진행하도록 parity 문서를 갱신했습니다.
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
- Document update: 2026-04-05 15:34 (KST) - Rebased the AX Agent improvement plan on actual `claw-code` runtime files instead of prior AX snapshots. The active reference spine is `src/bootstrap/state.ts -> src/bridge/initReplBridge.ts -> src/bridge/sessionRunner.ts -> src/screens/REPL.tsx -> src/components/Messages.tsx -> src/components/StatusLine.tsx`.
|
- Document update: 2026-04-05 15:34 (KST) - Rebased the AX Agent improvement plan on actual `claw-code` runtime files instead of prior AX snapshots. The active reference spine is `src/bootstrap/state.ts -> src/bridge/initReplBridge.ts -> src/bridge/sessionRunner.ts -> src/screens/REPL.tsx -> src/components/Messages.tsx -> src/components/StatusLine.tsx`.
|
||||||
- Document update: 2026-04-05 15:34 (KST) - Locked the AX implementation order to the same quality sequence used by that spine: runtime state canonicalization, prepared execution unification, loop event normalization, timeline render parity, composer/status strip simplification, and recovery/resume validation.
|
- Document update: 2026-04-05 15:34 (KST) - Locked the AX implementation order to the same quality sequence used by that spine: runtime state canonicalization, prepared execution unification, loop event normalization, timeline render parity, composer/status strip simplification, and recovery/resume validation.
|
||||||
|
- Document update: 2026-04-05 15:42 (KST) - Moved the cross-tab conversation restoration path for execution events and agent-run history from `ChatWindow.xaml.cs` into `AxAgentExecutionEngine`. `AppendExecutionEvent` and `AppendAgentRun` now go through one engine-owned session mutation helper, which preserves the active tab conversation while updating the target tab timeline.
|
||||||
|
- Document update: 2026-04-05 15:42 (KST) - Verified the first runtime-state/common-engine step with `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` and confirmed warning 0 / error 0.
|
||||||
- Document update: 2026-04-05 07:11 (KST) - Simplified the AX Agent footer for Cowork/Code by removing the duplicated `MoodIconPanel` chip group from those tabs and leaving workspace context only in the main folder path row. Also removed the outline border from the data-usage button so the footer option strip reads flatter and less pill-heavy.
|
- Document update: 2026-04-05 07:11 (KST) - Simplified the AX Agent footer for Cowork/Code by removing the duplicated `MoodIconPanel` chip group from those tabs and leaving workspace context only in the main folder path row. Also removed the outline border from the data-usage button so the footer option strip reads flatter and less pill-heavy.
|
||||||
- Document update: 2026-04-05 07:08 (KST) - Improved AX Agent responsiveness in three hot paths: added an ordered meta cache in `ChatStorageService` so repeated conversation-list refreshes stop re-sorting the full meta set every time, short-circuited `SaveConversationSettings()` when permission/data-usage/mood/output-format values are unchanged, and debounced the sidebar conversation search refresh to avoid re-filtering on every keystroke.
|
- Document update: 2026-04-05 07:08 (KST) - Improved AX Agent responsiveness in three hot paths: added an ordered meta cache in `ChatStorageService` so repeated conversation-list refreshes stop re-sorting the full meta set every time, short-circuited `SaveConversationSettings()` when permission/data-usage/mood/output-format values are unchanged, and debounced the sidebar conversation search refresh to avoid re-filtering on every keystroke.
|
||||||
- Document update: 2026-04-05 02:00 (KST) - Reworked the AX Agent in-chat gear overlay navigation itself to match the restored internal settings taxonomy: `basic / chat / cowork / code / dev / tools / skill-block`. The left nav labels now follow that scheme, and the overlay rows/toggles are regrouped per tab instead of the earlier `common / service / permission / advanced` split.
|
- Document update: 2026-04-05 02:00 (KST) - Reworked the AX Agent in-chat gear overlay navigation itself to match the restored internal settings taxonomy: `basic / chat / cowork / code / dev / tools / skill-block`. The left nav labels now follow that scheme, and the overlay rows/toggles are regrouped per tab instead of the earlier `common / service / permission / advanced` split.
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ public sealed class AxAgentExecutionEngine
|
|||||||
ExecutionMode Mode,
|
ExecutionMode Mode,
|
||||||
IReadOnlyList<string> PromptStack,
|
IReadOnlyList<string> PromptStack,
|
||||||
List<ChatMessage> Messages);
|
List<ChatMessage> Messages);
|
||||||
|
public sealed record SessionMutationResult(
|
||||||
|
ChatConversation CurrentConversation,
|
||||||
|
ChatConversation UpdatedConversation);
|
||||||
|
|
||||||
public IReadOnlyList<string> BuildPromptStack(
|
public IReadOnlyList<string> BuildPromptStack(
|
||||||
string? conversationSystem,
|
string? conversationSystem,
|
||||||
@@ -159,6 +162,42 @@ public sealed class AxAgentExecutionEngine
|
|||||||
return normalized;
|
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(
|
public string NormalizeAssistantContent(
|
||||||
ChatConversation conversation,
|
ChatConversation conversation,
|
||||||
string runTab,
|
string runTab,
|
||||||
@@ -208,4 +247,86 @@ public sealed class AxAgentExecutionEngine
|
|||||||
FileName = source.FileName,
|
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)
|
if (session == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var normalizedTarget = NormalizeTabName(targetTab);
|
var result = _chatEngine.AppendAgentRun(
|
||||||
var normalizedActive = NormalizeTabName(_activeTab);
|
session,
|
||||||
ChatConversation updatedConversation;
|
_storage,
|
||||||
if (string.Equals(normalizedTarget, normalizedActive, StringComparison.OrdinalIgnoreCase))
|
_currentConversation,
|
||||||
{
|
_activeTab,
|
||||||
_currentConversation = updatedConversation = session.AppendAgentRun(normalizedTarget, evt, status, summary, null);
|
targetTab,
|
||||||
ScheduleConversationPersist(updatedConversation);
|
evt,
|
||||||
return;
|
status,
|
||||||
}
|
summary);
|
||||||
|
_currentConversation = result.CurrentConversation;
|
||||||
var activeSnapshot = _currentConversation;
|
ScheduleConversationPersist(result.UpdatedConversation);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9219,48 +9188,15 @@ public partial class ChatWindow : Window
|
|||||||
if (session == null)
|
if (session == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var normalizedTarget = NormalizeTabName(targetTab);
|
var result = _chatEngine.AppendExecutionEvent(
|
||||||
var normalizedActive = NormalizeTabName(_activeTab);
|
session,
|
||||||
ChatConversation updatedConversation;
|
_storage,
|
||||||
if (string.Equals(normalizedTarget, normalizedActive, StringComparison.OrdinalIgnoreCase))
|
_currentConversation,
|
||||||
{
|
_activeTab,
|
||||||
_currentConversation = updatedConversation = session.AppendExecutionEvent(normalizedTarget, evt, null);
|
targetTab,
|
||||||
ScheduleConversationPersist(updatedConversation);
|
evt);
|
||||||
return;
|
_currentConversation = result.CurrentConversation;
|
||||||
}
|
ScheduleConversationPersist(result.UpdatedConversation);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user