Skip to main content

astrid_hooks/
hook_event.rs

1//! Hook event types.
2//!
3//! `HookEvent` is the canonical enum of lifecycle events that can trigger hooks.
4
5use serde::{Deserialize, Serialize};
6use std::fmt;
7
8/// Events that can trigger hooks.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
10#[serde(rename_all = "snake_case")]
11pub enum HookEvent {
12    /// Session has started.
13    SessionStart,
14    /// Session is ending.
15    SessionEnd,
16    /// User has submitted a prompt.
17    UserPrompt,
18    /// Before a tool call is executed.
19    PreToolCall,
20    /// After a tool call completes successfully.
21    PostToolCall,
22    /// A tool call resulted in an error.
23    ToolError,
24    /// Before an approval request is shown.
25    PreApproval,
26    /// After an approval decision is made.
27    PostApproval,
28    /// A notification needs to be sent.
29    Notification,
30    /// Before context compaction.
31    PreCompact,
32    /// After context compaction.
33    PostCompact,
34    /// Before the LLM prompt is assembled.
35    PromptBuild,
36    /// Before a response message is sent to the user.
37    MessageSend,
38    /// Before a session is reset.
39    SessionReset,
40    /// Before model/provider selection is resolved.
41    ModelResolve,
42    /// A user message has been received.
43    MessageReceived,
44    /// A response message has been delivered.
45    MessageSent,
46    /// The agent's cognitive loop has completed its run.
47    AgentLoopEnd,
48    /// A tool result is about to be persisted to history.
49    ToolResultPersist,
50    /// A subagent is starting.
51    SubagentStart,
52    /// A subagent has stopped.
53    SubagentStop,
54    /// Kernel daemon is starting.
55    KernelStart,
56    /// Kernel daemon is stopping.
57    KernelStop,
58}
59
60impl fmt::Display for HookEvent {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        match self {
63            Self::SessionStart => write!(f, "session_start"),
64            Self::SessionEnd => write!(f, "session_end"),
65            Self::UserPrompt => write!(f, "user_prompt"),
66            Self::PreToolCall => write!(f, "pre_tool_call"),
67            Self::PostToolCall => write!(f, "post_tool_call"),
68            Self::ToolError => write!(f, "tool_error"),
69            Self::PreApproval => write!(f, "pre_approval"),
70            Self::PostApproval => write!(f, "post_approval"),
71            Self::Notification => write!(f, "notification"),
72            Self::PreCompact => write!(f, "pre_compact"),
73            Self::PostCompact => write!(f, "post_compact"),
74            Self::PromptBuild => write!(f, "prompt_build"),
75            Self::MessageSend => write!(f, "message_send"),
76            Self::SessionReset => write!(f, "session_reset"),
77            Self::ModelResolve => write!(f, "model_resolve"),
78            Self::MessageReceived => write!(f, "message_received"),
79            Self::MessageSent => write!(f, "message_sent"),
80            Self::AgentLoopEnd => write!(f, "agent_loop_end"),
81            Self::ToolResultPersist => write!(f, "tool_result_persist"),
82            Self::SubagentStart => write!(f, "subagent_start"),
83            Self::SubagentStop => write!(f, "subagent_stop"),
84            Self::KernelStart => write!(f, "kernel_start"),
85            Self::KernelStop => write!(f, "kernel_stop"),
86        }
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn all_hook_events_have_display_and_serde_roundtrip() {
96        let hooks: Vec<HookEvent> = vec![
97            HookEvent::SessionStart,
98            HookEvent::SessionEnd,
99            HookEvent::UserPrompt,
100            HookEvent::PreToolCall,
101            HookEvent::PostToolCall,
102            HookEvent::ToolError,
103            HookEvent::PreApproval,
104            HookEvent::PostApproval,
105            HookEvent::Notification,
106            HookEvent::PreCompact,
107            HookEvent::PostCompact,
108            HookEvent::PromptBuild,
109            HookEvent::MessageSend,
110            HookEvent::SessionReset,
111            HookEvent::ModelResolve,
112            HookEvent::MessageReceived,
113            HookEvent::MessageSent,
114            HookEvent::AgentLoopEnd,
115            HookEvent::ToolResultPersist,
116            HookEvent::SubagentStart,
117            HookEvent::SubagentStop,
118            HookEvent::KernelStart,
119            HookEvent::KernelStop,
120        ];
121
122        for hook in &hooks {
123            let display = hook.to_string();
124            assert!(
125                !display.is_empty(),
126                "Display must not be empty for {hook:?}"
127            );
128
129            let json = serde_json::to_string(hook).unwrap();
130            let parsed: HookEvent = serde_json::from_str(&json).unwrap();
131            assert_eq!(*hook, parsed, "serde round-trip failed for {hook:?}");
132        }
133    }
134}