From 65f222b90be7d72199f46df839d959d8e08e1cc8 Mon Sep 17 00:00:00 2001 From: Andrei Hasna Date: Fri, 24 Jul 2026 14:50:22 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20Add=20more=20hook=20types=20to=20codewi?= =?UTF-8?q?th:=20onError,=20onSafetyFlag,=20and=20onNewAgentThread=20(exte?= =?UTF-8?q?nsible=20hook=20framework).=20=E2=80=94=20Start=20a=20goal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codex-rs/config/src/hook_config.rs | 26 ++++++++++++++++++++++++- codex-rs/hooks/src/engine/dispatcher.rs | 14 ++++++++++--- codex-rs/hooks/src/engine/mod.rs | 3 +++ codex-rs/hooks/src/events/common.rs | 6 +++++- codex-rs/hooks/src/lib.rs | 8 +++++++- codex-rs/protocol/src/protocol.rs | 3 +++ 6 files changed, 54 insertions(+), 6 deletions(-) diff --git a/codex-rs/config/src/hook_config.rs b/codex-rs/config/src/hook_config.rs index eee66896af..bbece1e6e8 100644 --- a/codex-rs/config/src/hook_config.rs +++ b/codex-rs/config/src/hook_config.rs @@ -51,6 +51,12 @@ pub struct HookEventsToml { pub subagent_stop: Vec, #[serde(rename = "Stop", default)] pub stop: Vec, + #[serde(rename = "OnError", default)] + pub on_error: Vec, + #[serde(rename = "OnSafetyFlag", default)] + pub on_safety_flag: Vec, + #[serde(rename = "OnNewAgentThread", default)] + pub on_new_agent_thread: Vec, } impl HookEventsToml { @@ -66,6 +72,9 @@ impl HookEventsToml { subagent_start, subagent_stop, stop, + on_error, + on_safety_flag, + on_new_agent_thread, } = self; pre_tool_use.is_empty() && permission_request.is_empty() @@ -77,6 +86,9 @@ impl HookEventsToml { && subagent_start.is_empty() && subagent_stop.is_empty() && stop.is_empty() + && on_error.is_empty() + && on_safety_flag.is_empty() + && on_new_agent_thread.is_empty() } pub fn handler_count(&self) -> usize { @@ -91,6 +103,9 @@ impl HookEventsToml { subagent_start, subagent_stop, stop, + on_error, + on_safety_flag, + on_new_agent_thread, } = self; [ pre_tool_use, @@ -103,6 +118,9 @@ impl HookEventsToml { subagent_start, subagent_stop, stop, + on_error, + on_safety_flag, + on_new_agent_thread, ] .into_iter() .flatten() @@ -110,7 +128,7 @@ impl HookEventsToml { .sum() } - pub fn into_matcher_groups(self) -> [(HookEventName, Vec); 10] { + pub fn into_matcher_groups(self) -> [(HookEventName, Vec); 13] { [ (HookEventName::PreToolUse, self.pre_tool_use), (HookEventName::PermissionRequest, self.permission_request), @@ -122,6 +140,12 @@ impl HookEventsToml { (HookEventName::SubagentStart, self.subagent_start), (HookEventName::SubagentStop, self.subagent_stop), (HookEventName::Stop, self.stop), + (HookEventName::OnError, self.on_error), + (HookEventName::OnSafetyFlag, self.on_safety_flag), + ( + HookEventName::OnNewAgentThread, + self.on_new_agent_thread, + ), ] } } diff --git a/codex-rs/hooks/src/engine/dispatcher.rs b/codex-rs/hooks/src/engine/dispatcher.rs index 50822bfc96..dc2790a602 100644 --- a/codex-rs/hooks/src/engine/dispatcher.rs +++ b/codex-rs/hooks/src/engine/dispatcher.rs @@ -61,7 +61,11 @@ pub(crate) fn select_handlers_for_matcher_inputs( .any(|input| matches_matcher(handler.matcher.as_deref(), Some(input))) } } - HookEventName::UserPromptSubmit | HookEventName::Stop => true, + HookEventName::UserPromptSubmit + | HookEventName::Stop + | HookEventName::OnError + | HookEventName::OnSafetyFlag + | HookEventName::OnNewAgentThread => true, }) .cloned() .collect() @@ -141,7 +145,9 @@ pub(crate) fn completed_summary( fn scope_for_event(event_name: HookEventName) -> HookScope { match event_name { - HookEventName::SessionStart | HookEventName::SubagentStart => HookScope::Thread, + HookEventName::SessionStart + | HookEventName::SubagentStart + | HookEventName::OnNewAgentThread => HookScope::Thread, HookEventName::PreToolUse | HookEventName::PermissionRequest | HookEventName::PostToolUse @@ -149,7 +155,9 @@ fn scope_for_event(event_name: HookEventName) -> HookScope { | HookEventName::PostCompact | HookEventName::UserPromptSubmit | HookEventName::SubagentStop - | HookEventName::Stop => HookScope::Turn, + | HookEventName::Stop + | HookEventName::OnError + | HookEventName::OnSafetyFlag => HookScope::Turn, } } diff --git a/codex-rs/hooks/src/engine/mod.rs b/codex-rs/hooks/src/engine/mod.rs index 859fc54069..b0a8a76000 100644 --- a/codex-rs/hooks/src/engine/mod.rs +++ b/codex-rs/hooks/src/engine/mod.rs @@ -73,6 +73,9 @@ impl ConfiguredHandler { codex_protocol::protocol::HookEventName::SubagentStart => "subagent-start", codex_protocol::protocol::HookEventName::SubagentStop => "subagent-stop", codex_protocol::protocol::HookEventName::Stop => "stop", + codex_protocol::protocol::HookEventName::OnError => "on-error", + codex_protocol::protocol::HookEventName::OnSafetyFlag => "on-safety-flag", + codex_protocol::protocol::HookEventName::OnNewAgentThread => "on-new-agent-thread", } } } diff --git a/codex-rs/hooks/src/events/common.rs b/codex-rs/hooks/src/events/common.rs index 997eac139f..402e745a57 100644 --- a/codex-rs/hooks/src/events/common.rs +++ b/codex-rs/hooks/src/events/common.rs @@ -115,7 +115,11 @@ pub(crate) fn matcher_pattern_for_event( | HookEventName::SubagentStop | HookEventName::PreCompact | HookEventName::PostCompact => matcher, - HookEventName::UserPromptSubmit | HookEventName::Stop => None, + HookEventName::UserPromptSubmit + | HookEventName::Stop + | HookEventName::OnError + | HookEventName::OnSafetyFlag + | HookEventName::OnNewAgentThread => None, } } diff --git a/codex-rs/hooks/src/lib.rs b/codex-rs/hooks/src/lib.rs index 5ea4b1abdd..b166c5b2bc 100644 --- a/codex-rs/hooks/src/lib.rs +++ b/codex-rs/hooks/src/lib.rs @@ -27,7 +27,7 @@ pub use fleet_comms::fleet_comms_session_start_matcher_group; pub use fleet_comms::fleet_comms_session_start_state_key; pub use fleet_comms::fleet_comms_session_start_trusted_hash; /// Hook event names as they appear in hooks JSON and config files. -pub const HOOK_EVENT_NAMES: [&str; 10] = [ +pub const HOOK_EVENT_NAMES: [&str; 13] = [ "PreToolUse", "PermissionRequest", "PostToolUse", @@ -38,6 +38,9 @@ pub const HOOK_EVENT_NAMES: [&str; 10] = [ "SubagentStart", "SubagentStop", "Stop", + "OnError", + "OnSafetyFlag", + "OnNewAgentThread", ]; /// Hook event names whose matcher fields are meaningful during dispatch. @@ -104,6 +107,9 @@ pub fn hook_event_key_label(event_name: HookEventName) -> &'static str { HookEventName::SubagentStart => "subagent_start", HookEventName::SubagentStop => "subagent_stop", HookEventName::Stop => "stop", + HookEventName::OnError => "on_error", + HookEventName::OnSafetyFlag => "on_safety_flag", + HookEventName::OnNewAgentThread => "on_new_agent_thread", } } diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index 9cb8d49220..b8b8932946 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -1455,6 +1455,9 @@ pub enum HookEventName { SubagentStart, SubagentStop, Stop, + OnError, + OnSafetyFlag, + OnNewAgentThread, } #[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)]