use serde_json::Value;
use super::{str_field, STEER_EVENTS};
const QUEUE_EVENTS: [&str; 3] = ["UserPromptSubmit", "Stop", "SubagentStop"];
const REENTRY_SOURCES: [&str; 2] = ["resume", "compact"];
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct HookInput {
pub(crate) session_id: String,
pub(crate) transcript_path: String,
pub(crate) cwd: Option<String>,
pub(crate) agent_id: Option<String>,
pub(crate) agent_type: Option<String>,
pub(crate) hook_event_name: String,
pub(crate) source: Option<String>,
pub(crate) stop_hook_active: bool,
pub(crate) agent_transcript_path: Option<String>,
}
impl HookInput {
pub(crate) fn parse(raw: &str) -> Option<Self> {
let v: Value = serde_json::from_str(raw).ok()?;
if !v.is_object() {
return None;
}
Some(HookInput {
session_id: str_field(&v, "session_id").unwrap_or_default(),
transcript_path: str_field(&v, "transcript_path").unwrap_or_default(),
cwd: str_field(&v, "cwd"),
agent_id: str_field(&v, "agent_id").filter(|s| !s.trim().is_empty()),
agent_type: str_field(&v, "agent_type"),
hook_event_name: str_field(&v, "hook_event_name").unwrap_or_default(),
source: str_field(&v, "source"),
stop_hook_active: v
.get("stop_hook_active")
.and_then(Value::as_bool)
.unwrap_or(false),
agent_transcript_path: str_field(&v, "agent_transcript_path"),
})
}
pub(crate) fn lane(&self) -> &str {
self.agent_id.as_deref().unwrap_or(&self.session_id)
}
pub(crate) fn is_stop_family(&self) -> bool {
self.hook_event_name == "Stop" || self.hook_event_name == "SubagentStop"
}
pub(crate) fn session_start_source(&self, want: &str) -> bool {
self.hook_event_name == "SessionStart" && self.source.as_deref() == Some(want)
}
}
pub(crate) fn is_steer_event(event: &str) -> bool {
STEER_EVENTS.contains(&event)
}
pub(crate) fn is_queue_event(event: &str, source: Option<&str>) -> bool {
if QUEUE_EVENTS.contains(&event) {
return true;
}
event == "SessionStart" && source.is_some_and(|s| REENTRY_SOURCES.contains(&s))
}