pub(crate) mod command_runner;pub(crate) mod discovery;pub(crate) mod dispatcher;pub(crate) mod output_parser;pub(crate) mod schema_loader;use crate::events::compact::PostCompactRequest;use crate::events::compact::PreCompactOutcome;use crate::events::compact::PreCompactRequest;use crate::events::compact::StatelessHookOutcome;use crate::events::permission_request::PermissionRequestOutcome;use crate::events::permission_request::PermissionRequestRequest;use crate::events::post_tool_use::PostToolUseOutcome;use crate::events::post_tool_use::PostToolUseRequest;use crate::events::pre_tool_use::PreToolUseOutcome;use crate::events::pre_tool_use::PreToolUseRequest;use crate::events::session_start::SessionStartOutcome;use crate::events::session_start::SessionStartRequest;use crate::events::stop::StopOutcome;use crate::events::stop::StopRequest;use crate::events::user_prompt_submit::UserPromptSubmitOutcome;use crate::events::user_prompt_submit::UserPromptSubmitRequest;use crate::output_spill::HookOutputSpiller;use codex_config::ConfigLayerStack;use codex_plugin::PluginHookSource;use codex_protocol::ThreadId;use codex_protocol::protocol::HookEventName;use codex_protocol::protocol::HookHandlerType;use codex_protocol::protocol::HookRunSummary;use codex_protocol::protocol::HookSource;use codex_protocol::protocol::HookTrustStatus;use codex_utils_absolute_path::AbsolutePathBuf;use std::collections::HashMap;#[derive(Debug, Clone)]pub(crate) struct CommandShell { pub program: String, pub args: Vec<String>,}#[derive(Debug, Clone, PartialEq, Eq)]pub(crate) struct ConfiguredHandler { pub event_name: codex_protocol::protocol::HookEventName, pub matcher: Option<String>, pub command: String, pub timeout_sec: u64, pub status_message: Option<String>, pub source_path: AbsolutePathBuf, pub source: HookSource, pub display_order: i64, pub env: HashMap<String, String>,}impl ConfiguredHandler { pub fn run_id(&self) -> String { format!( "{}:{}:{}", self.event_name_label(), self.display_order, self.source_path.display() ) } fn event_name_label(&self) -> &'static str { match self.event_name { codex_protocol::protocol::HookEventName::PreToolUse => "pre-tool-use", codex_protocol::protocol::HookEventName::PermissionRequest => "permission-request", codex_protocol::protocol::HookEventName::PostToolUse => "post-tool-use", codex_protocol::protocol::HookEventName::PreCompact => "pre-compact", codex_protocol::protocol::HookEventName::PostCompact => "post-compact", codex_protocol::protocol::HookEventName::SessionStart => "session-start", codex_protocol::protocol::HookEventName::UserPromptSubmit => "user-prompt-submit", codex_protocol::protocol::HookEventName::SubagentStart => "subagent-start", codex_protocol::protocol::HookEventName::SubagentStop => "subagent-stop", codex_protocol::protocol::HookEventName::Stop => "stop", } }}#[derive(Debug, Clone, PartialEq, Eq)]pub struct HookListEntry { pub key: String, pub event_name: HookEventName, pub handler_type: HookHandlerType, pub matcher: Option<String>, pub command: Option<String>, pub timeout_sec: u64, pub status_message: Option<String>, pub source_path: AbsolutePathBuf, pub source: HookSource, pub plugin_id: Option<String>, pub display_order: i64, pub enabled: bool, pub is_managed: bool, pub current_hash: String, pub trust_status: HookTrustStatus,}#[derive(Clone)]pub(crate) struct ClaudeHooksEngine { handlers: Vec<ConfiguredHandler>, warnings: Vec<String>, shell: CommandShell, output_spiller: HookOutputSpiller,}impl ClaudeHooksEngine { pub(crate) fn new( enabled: bool, bypass_hook_trust: bool, config_layer_stack: Option<&ConfigLayerStack>, plugin_hook_sources: Vec<PluginHookSource>, plugin_hook_load_warnings: Vec<String>, shell: CommandShell, ) -> Self { if !enabled { return Self { handlers: Vec::new(), warnings: Vec::new(), shell, output_spiller: HookOutputSpiller::new(), }; } let _ = schema_loader::generated_hook_schemas(); let discovered = discovery::discover_handlers( config_layer_stack, plugin_hook_sources, plugin_hook_load_warnings, bypass_hook_trust, ); Self { handlers: discovered.handlers, warnings: discovered.warnings, shell, output_spiller: HookOutputSpiller::new(), } } pub(crate) fn warnings(&self) -> &[String] { &self.warnings } pub(crate) fn preview_session_start( &self, request: &SessionStartRequest, ) -> Vec<HookRunSummary> { crate::events::session_start::preview(&self.handlers, request) } pub(crate) fn preview_pre_tool_use(&self, request: &PreToolUseRequest) -> Vec<HookRunSummary> { crate::events::pre_tool_use::preview(&self.handlers, request) } pub(crate) fn preview_permission_request( &self, request: &PermissionRequestRequest, ) -> Vec<HookRunSummary> { crate::events::permission_request::preview(&self.handlers, request) } pub(crate) fn preview_post_tool_use( &self, request: &PostToolUseRequest, ) -> Vec<HookRunSummary> { crate::events::post_tool_use::preview(&self.handlers, request) } pub(crate) async fn run_session_start( &self, request: SessionStartRequest, turn_id: Option<String>, ) -> SessionStartOutcome { let session_id = request.session_id; let mut outcome = crate::events::session_start::run(&self.handlers, &self.shell, request, turn_id).await; outcome.additional_contexts = self .maybe_spill_texts(session_id, outcome.additional_contexts) .await; outcome } pub(crate) async fn run_pre_tool_use(&self, request: PreToolUseRequest) -> PreToolUseOutcome { let session_id = request.session_id; let mut outcome = crate::events::pre_tool_use::run(&self.handlers, &self.shell, request).await; outcome.additional_contexts = self .maybe_spill_texts(session_id, outcome.additional_contexts) .await; outcome } pub(crate) async fn run_permission_request( &self, request: PermissionRequestRequest, ) -> PermissionRequestOutcome { crate::events::permission_request::run(&self.handlers, &self.shell, request).await } pub(crate) async fn run_post_tool_use( &self, request: PostToolUseRequest, ) -> PostToolUseOutcome { let session_id = request.session_id; let mut outcome = crate::events::post_tool_use::run(&self.handlers, &self.shell, request).await; outcome.additional_contexts = self .maybe_spill_texts(session_id, outcome.additional_contexts) .await; outcome.feedback_message = self .maybe_spill_text(session_id, outcome.feedback_message) .await; outcome } pub(crate) fn preview_pre_compact(&self, request: &PreCompactRequest) -> Vec<HookRunSummary> { crate::events::compact::preview_pre(&self.handlers, request) } pub(crate) async fn run_pre_compact(&self, request: PreCompactRequest) -> PreCompactOutcome { crate::events::compact::run_pre(&self.handlers, &self.shell, request).await } pub(crate) fn preview_post_compact(&self, request: &PostCompactRequest) -> Vec<HookRunSummary> { crate::events::compact::preview_post(&self.handlers, request) } pub(crate) async fn run_post_compact( &self, request: PostCompactRequest, ) -> StatelessHookOutcome { crate::events::compact::run_post(&self.handlers, &self.shell, request).await } pub(crate) fn preview_user_prompt_submit( &self, request: &UserPromptSubmitRequest, ) -> Vec<HookRunSummary> { crate::events::user_prompt_submit::preview(&self.handlers, request) } pub(crate) async fn run_user_prompt_submit( &self, request: UserPromptSubmitRequest, ) -> UserPromptSubmitOutcome { let session_id = request.session_id; let mut outcome = crate::events::user_prompt_submit::run(&self.handlers, &self.shell, request).await; outcome.additional_contexts = self .maybe_spill_texts(session_id, outcome.additional_contexts) .await; outcome } pub(crate) fn preview_stop(&self, request: &StopRequest) -> Vec<HookRunSummary> { crate::events::stop::preview(&self.handlers, request) } pub(crate) async fn run_stop(&self, request: StopRequest) -> StopOutcome { let session_id = request.session_id; let mut outcome = crate::events::stop::run(&self.handlers, &self.shell, request).await; outcome.continuation_fragments = self .maybe_spill_prompt_fragments(session_id, outcome.continuation_fragments) .await; outcome } async fn maybe_spill_texts(&self, session_id: ThreadId, texts: Vec<String>) -> Vec<String> { self.output_spiller .maybe_spill_texts(session_id, texts) .await } async fn maybe_spill_text(&self, session_id: ThreadId, text: Option<String>) -> Option<String> { match text { Some(text) => Some(self.output_spiller.maybe_spill_text(session_id, text).await), None => None, } } async fn maybe_spill_prompt_fragments( &self, session_id: ThreadId, fragments: Vec<codex_protocol::items::HookPromptFragment>, ) -> Vec<codex_protocol::items::HookPromptFragment> { self.output_spiller .maybe_spill_prompt_fragments(session_id, fragments) .await }}#[cfg(test)]#[path = "mod_tests.rs"]mod tests;