Codex Handbook
hooks/src/engine/mod.rs 294 lines
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;