diff --git a/src/AxCopilot/Services/Agent/AgentLoopService.Skills.cs b/src/AxCopilot/Services/Agent/AgentLoopService.Skills.cs
index b796151..90762f7 100644
--- a/src/AxCopilot/Services/Agent/AgentLoopService.Skills.cs
+++ b/src/AxCopilot/Services/Agent/AgentLoopService.Skills.cs
@@ -98,9 +98,17 @@ public partial class AgentLoopService
EmitEvent(AgentEventType.Thinking, "skill_fork",
$"[Fork] '{skill.Label}' 스킬을 격리 컨텍스트에서 실행 중...");
+ // Phase 17-C: PreSkillExecute 훅 발화
+ _ = RunExtendedEventAsync(HookEventKind.PreSkillExecute, forkMessages, ct,
+ toolName: skill.Name, toolInput: preparedBody);
+
// 도구 없이 텍스트 응답만 생성 (격리 실행)
var response = await _llm.SendAsync(forkMessages, ct);
+ // Phase 17-C: PostSkillExecute 훅 발화 (fire-and-forget)
+ _ = RunExtendedEventAsync(HookEventKind.PostSkillExecute, null, CancellationToken.None,
+ toolName: skill.Name, toolOutput: response ?? "");
+
// SkillCompleted 이벤트 로그 기록
_ = _eventLog?.AppendAsync(AgentEventLogType.SkillCompleted,
System.Text.Json.JsonSerializer.Serialize(new
diff --git a/src/AxCopilot/Views/ChatWindow.SlashCommands.cs b/src/AxCopilot/Views/ChatWindow.SlashCommands.cs
index 65c5b9d..e76accd 100644
--- a/src/AxCopilot/Views/ChatWindow.SlashCommands.cs
+++ b/src/AxCopilot/Views/ChatWindow.SlashCommands.cs
@@ -411,4 +411,19 @@ public partial class ChatWindow
return (null, input);
}
+
+ ///
+ /// 외부에서 슬래시 명령을 입력창에 주입하여 실행합니다.
+ /// AgentSettingsPanel 등에서 /hooks 조회 등에 사용합니다.
+ ///
+ public void InjectSlashCommand(string slashCmd)
+ {
+ Dispatcher.Invoke(() =>
+ {
+ SlashPopup.IsOpen = false;
+ ShowSlashChip(slashCmd);
+ InputBox.Text = "";
+ _ = SendMessageAsync();
+ });
+ }
}
diff --git a/src/AxCopilot/Views/ChatWindow.WorkFolder.cs b/src/AxCopilot/Views/ChatWindow.WorkFolder.cs
index 68fd189..043f8f6 100644
--- a/src/AxCopilot/Views/ChatWindow.WorkFolder.cs
+++ b/src/AxCopilot/Views/ChatWindow.WorkFolder.cs
@@ -208,6 +208,13 @@ public partial class ChatWindow
if (recent.Count > maxRecent) recent.RemoveRange(maxRecent, recent.Count - maxRecent);
Llm.WorkFolder = path;
_settings.Save();
+
+ // Phase 17-C: CwdChanged 훅 발화 (fire-and-forget)
+ _ = _agentLoop.RunExtendedEventAsync(
+ Services.Agent.HookEventKind.CwdChanged,
+ null,
+ CancellationToken.None,
+ toolInput: path);
}
private string GetCurrentWorkFolder()
diff --git a/src/AxCopilot/Views/Controls/AgentSettingsPanel.xaml b/src/AxCopilot/Views/Controls/AgentSettingsPanel.xaml
index 69d9bf4..b3443c3 100644
--- a/src/AxCopilot/Views/Controls/AgentSettingsPanel.xaml
+++ b/src/AxCopilot/Views/Controls/AgentSettingsPanel.xaml
@@ -485,6 +485,85 @@
Checked="ChkDevMode_Changed" Unchecked="ChkDevMode_Changed"/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/AxCopilot/Views/Controls/AgentSettingsPanel.xaml.cs b/src/AxCopilot/Views/Controls/AgentSettingsPanel.xaml.cs
index 8e33564..9599296 100644
--- a/src/AxCopilot/Views/Controls/AgentSettingsPanel.xaml.cs
+++ b/src/AxCopilot/Views/Controls/AgentSettingsPanel.xaml.cs
@@ -115,6 +115,11 @@ public partial class AgentSettingsPanel : UserControl
// MCP 서버 목록 표시
BuildMcpServerList(settings);
+ // Phase 17-C: 훅 UI 초기화
+ if (ChkExtendedHooks != null)
+ ChkExtendedHooks.IsChecked = llm.EnableToolHooks;
+ RefreshHookSummary();
+
_isLoading = false;
}
@@ -356,6 +361,80 @@ public partial class AgentSettingsPanel : UserControl
SaveSetting(s => s.Llm.DevMode = ChkDevMode.IsChecked == true);
}
+ // ── Phase 17-C: 훅 이벤트 UI ─────────────────────────────────────────
+
+ private void ChkExtendedHooks_Changed(object sender, RoutedEventArgs e)
+ {
+ if (_isLoading) return;
+ SaveSetting(s => s.Llm.EnableToolHooks = ChkExtendedHooks.IsChecked == true);
+ RefreshHookSummary();
+ }
+
+ private void BtnViewHooks_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
+ {
+ // ChatWindow에서 /hooks 슬래시 명령 실행
+ var chatWin = System.Windows.Application.Current?.Windows
+ .OfType()
+ .FirstOrDefault(w => w.IsVisible);
+ chatWin?.InjectSlashCommand("/hooks");
+ }
+
+ private void BtnOpenHookSettings_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
+ {
+ // settings.json 파일을 기본 편집기로 열기
+ var path = System.IO.Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
+ "AxCopilot", "settings.dat");
+ if (System.IO.File.Exists(path))
+ System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(path)
+ { UseShellExecute = true });
+ }
+
+ private void RefreshHookSummary()
+ {
+ if (HookSummaryText == null) return;
+
+ var settings = CurrentApp?.SettingsService?.Settings;
+ if (settings == null) { HookSummaryText.Text = "설정 로드 실패"; return; }
+
+ if (!settings.Llm.EnableToolHooks)
+ {
+ HookSummaryText.Text = "확장 훅 비활성화됨";
+ return;
+ }
+
+ var hooks = settings.Llm.ExtendedHooks;
+ var counts = new[]
+ {
+ hooks.PreToolUse.Count(h => h.Enabled),
+ hooks.PostToolUse.Count(h => h.Enabled),
+ hooks.PostToolUseFailure.Count(h => h.Enabled),
+ hooks.SessionStart.Count(h => h.Enabled),
+ hooks.SessionEnd.Count(h => h.Enabled),
+ hooks.UserPromptSubmit.Count(h => h.Enabled),
+ hooks.AgentStop.Count(h => h.Enabled),
+ hooks.PreCompact.Count(h => h.Enabled),
+ hooks.PostCompact.Count(h => h.Enabled),
+ hooks.FileChanged.Count(h => h.Enabled),
+ hooks.PermissionRequest.Count(h => h.Enabled),
+ };
+
+ var total = counts.Sum();
+ if (total == 0)
+ {
+ HookSummaryText.Text = "등록된 훅 없음 · settings.json extended_hooks에서 추가";
+ return;
+ }
+
+ var parts = new List();
+ if (hooks.PreToolUse.Any(h => h.Enabled)) parts.Add($"PreTool×{hooks.PreToolUse.Count(h => h.Enabled)}");
+ if (hooks.PostToolUse.Any(h => h.Enabled)) parts.Add($"PostTool×{hooks.PostToolUse.Count(h => h.Enabled)}");
+ if (hooks.SessionStart.Any(h => h.Enabled)) parts.Add($"Session×{hooks.SessionStart.Count(h => h.Enabled)}");
+ if (hooks.UserPromptSubmit.Any(h => h.Enabled)) parts.Add($"Prompt×{hooks.UserPromptSubmit.Count(h => h.Enabled)}");
+
+ HookSummaryText.Text = $"활성 훅 {total}개 · {string.Join(", ", parts)}";
+ }
+
// ── Phase 17-A: Reflexion 핸들러 ──────────────────────────────────────
private void ChkReflexion_Changed(object sender, RoutedEventArgs e)