diff --git a/src/event/compat.rs b/src/event/compat.rs index d68f404..b7db7e5 100644 --- a/src/event/compat.rs +++ b/src/event/compat.rs @@ -95,6 +95,12 @@ fn body_for(kind: &str, payload: &Value) -> Result { "session.test-finished" => Ok(EventBody::AgentTestFinished(agent_event(payload)?)), "session.test-failed" => Ok(EventBody::AgentTestFailed(agent_event(payload)?)), "session.handoff-needed" => Ok(EventBody::AgentHandoffNeeded(agent_event(payload)?)), + "session.prompt-submitted" => Ok(EventBody::AgentPromptSubmitted(agent_event(payload)?)), + "session.prompt-delivered" => Ok(EventBody::AgentPromptDelivered(agent_event(payload)?)), + "session.prompt-delivery-failed" => { + Ok(EventBody::AgentPromptDeliveryFailed(agent_event(payload)?)) + } + "session.stopped" => Ok(EventBody::AgentStopped(agent_event(payload)?)), "workspace.session.started" | "workspace.session.ended" => Ok( EventBody::WorkspaceSessionStarted(workspace_event(payload)?), ), @@ -368,13 +374,16 @@ fn workspace_event(payload: &Value) -> Result { fn priority_for(kind: &str, payload: &Value) -> EventPriority { match kind { - "agent.failed" | "session.failed" | "session.test-failed" | "github.ci-failed" => { - EventPriority::Critical - } + "agent.failed" + | "session.failed" + | "session.test-failed" + | "session.prompt-delivery-failed" + | "github.ci-failed" => EventPriority::Critical, "agent.blocked" | "session.blocked" | "session.retry-needed" | "session.handoff-needed" + | "session.stopped" | "tmux.stale" | "workspace.session.blocked" => EventPriority::High, "github.release-published" | "github.release-prereleased" => EventPriority::High, @@ -613,6 +622,22 @@ mod tests { "session.handoff-needed", EventBody::AgentHandoffNeeded(sample_agent_event("handoff-needed")), ), + ( + "session.prompt-submitted", + EventBody::AgentPromptSubmitted(sample_agent_event("prompt-submitted")), + ), + ( + "session.prompt-delivered", + EventBody::AgentPromptDelivered(sample_agent_event("prompt-delivered")), + ), + ( + "session.prompt-delivery-failed", + EventBody::AgentPromptDeliveryFailed(sample_agent_event("prompt-delivery-failed")), + ), + ( + "session.stopped", + EventBody::AgentStopped(sample_agent_event("stopped")), + ), ]; for (kind, expected) in cases { @@ -713,6 +738,10 @@ mod tests { EventBody::AgentTestFinished(_) => "test-finished", EventBody::AgentTestFailed(_) => "test-failed", EventBody::AgentHandoffNeeded(_) => "handoff-needed", + EventBody::AgentPromptSubmitted(_) => "prompt-submitted", + EventBody::AgentPromptDelivered(_) => "prompt-delivered", + EventBody::AgentPromptDeliveryFailed(_) => "prompt-delivery-failed", + EventBody::AgentStopped(_) => "stopped", _ => unreachable!(), } } diff --git a/src/event/mod.rs b/src/event/mod.rs index 3e9d59b..0b8fa56 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -49,6 +49,10 @@ pub enum EventBody { AgentTestFinished(AgentEvent), AgentTestFailed(AgentEvent), AgentHandoffNeeded(AgentEvent), + AgentPromptSubmitted(AgentEvent), + AgentPromptDelivered(AgentEvent), + AgentPromptDeliveryFailed(AgentEvent), + AgentStopped(AgentEvent), WorkspaceSessionStarted(WorkspaceEvent), WorkspaceTurnComplete(WorkspaceEvent), WorkspaceSkillActivated(WorkspaceEvent), diff --git a/src/events.rs b/src/events.rs index df73f89..420e80d 100644 --- a/src/events.rs +++ b/src/events.rs @@ -667,6 +667,10 @@ impl IncomingEvent { "test-finished" => "session.test-finished", "test-failed" => "session.test-failed", "handoff-needed" => "session.handoff-needed", + "prompt-submitted" => "session.prompt-submitted", + "prompt-delivered" => "session.prompt-delivered", + "prompt-delivery-failed" => "session.prompt-delivery-failed", + "stopped" => "session.stopped", other => other, } } @@ -819,6 +823,19 @@ fn map_native_signal(raw: &str) -> Option<&'static str> { } "test-failed" | "session.test-failed" | "test.failed" => Some("session.test-failed"), "handoff-needed" | "session.handoff-needed" => Some("session.handoff-needed"), + "stop" | "stopped" | "session.stopped" => Some("session.stopped"), + "userpromptsubmit" + | "user-prompt-submit" + | "user-prompt-submitted" + | "prompt-submitted" + | "prompt.submitted" + | "session.prompt-submitted" => Some("session.prompt-submitted"), + "prompt-delivered" | "session.prompt-delivered" | "first-prompt-delivered" => { + Some("session.prompt-delivered") + } + "prompt-delivery-failed" + | "session.prompt-delivery-failed" + | "first-prompt-delivery-failed" => Some("session.prompt-delivery-failed"), _ => None, } } @@ -1036,7 +1053,8 @@ fn normalize_native_metadata(payload: &mut Value, raw_kind: &str, canonical_kind insert_string_if_missing(object, "tool_name", tool_name); insert_string_if_missing(object, "test_runner", test_runner); insert_u64_if_missing(object, "elapsed_secs", elapsed_secs); - insert_string_if_missing(object, "status", status); + insert_string_if_missing(object, "status", status.clone()); + insert_string_if_missing(object, "normalized_event", status); insert_string_if_missing(object, "summary", summary); insert_string_if_missing(object, "error_message", error_message); insert_string_if_missing(object, "event_timestamp", event_timestamp); @@ -1111,6 +1129,10 @@ fn event_status_from_kind(kind: &str) -> Option<&'static str> { "session.test-finished" => Some("test-finished"), "session.test-failed" => Some("test-failed"), "session.handoff-needed" => Some("handoff-needed"), + "session.prompt-submitted" => Some("prompt-submitted"), + "session.prompt-delivered" => Some("prompt-delivered"), + "session.prompt-delivery-failed" => Some("prompt-delivery-failed"), + "session.stopped" => Some("stopped"), _ => None, } } @@ -1968,4 +1990,69 @@ mod tests { "[tmux:issue-24] 'error': build failed ยท 'complete': job complete" ); } + + #[test] + fn canonical_kind_maps_prompt_lifecycle_aliases() { + let cases = [ + ("prompt-submitted", "session.prompt-submitted"), + ("prompt-delivered", "session.prompt-delivered"), + ("prompt-delivery-failed", "session.prompt-delivery-failed"), + ("stopped", "session.stopped"), + ]; + + for (kind, expected) in cases { + let event = IncomingEvent { + kind: kind.into(), + channel: None, + mention: None, + format: None, + template: None, + payload: json!({}), + }; + assert_eq!( + event.canonical_kind(), + expected, + "unexpected canonical kind for {kind}" + ); + } + } + + #[test] + fn normalize_event_maps_native_prompt_and_stop_signals() { + let cases = [ + ( + "user-prompt-submit", + json!({}), + "session.prompt-submitted", + "prompt-submitted", + ), + ( + "notify", + json!({"normalized_event": "prompt-delivered"}), + "session.prompt-delivered", + "prompt-delivered", + ), + ( + "notify", + json!({"route_key": "first-prompt-delivery-failed"}), + "session.prompt-delivery-failed", + "prompt-delivery-failed", + ), + ("stop", json!({}), "session.stopped", "stopped"), + ]; + + for (kind, payload, expected_kind, expected_status) in cases { + let event = normalize_event(IncomingEvent { + kind: kind.into(), + channel: None, + mention: None, + format: None, + template: None, + payload, + }); + assert_eq!(event.kind, expected_kind); + assert_eq!(event.payload["status"], json!(expected_status)); + assert_eq!(event.payload["normalized_event"], json!(expected_status)); + } + } } diff --git a/src/native_hooks.rs b/src/native_hooks.rs index fa33e22..8c68460 100644 --- a/src/native_hooks.rs +++ b/src/native_hooks.rs @@ -396,7 +396,10 @@ fn map_shared_event(value: &str) -> Option<&'static str> { "sessionstart" | "session-start" | "session.started" | "started" => Some("session.started"), "pretooluse" | "pre-tool-use" => Some("tool.pre"), "posttooluse" | "post-tool-use" => Some("tool.post"), - "userpromptsubmit" | "user-prompt-submit" => Some("prompt.submitted"), + "userpromptsubmit" + | "user-prompt-submit" + | "prompt-submitted" + | "session.prompt-submitted" => Some("session.prompt-submitted"), "stop" | "sessionstop" | "session-stopped" => Some("session.stopped"), _ => None, } @@ -407,7 +410,7 @@ fn normalized_event_label(kind: &str) -> &str { "session.started" => "started", "tool.pre" => "pre-tool-use", "tool.post" => "post-tool-use", - "prompt.submitted" => "user-prompt-submit", + "session.prompt-submitted" => "prompt-submitted", "session.stopped" => "stop", _ => kind, } @@ -552,7 +555,11 @@ mod tests { ("SessionStart", "codex", "session.started"), ("PreToolUse", "codex", "tool.pre"), ("PostToolUse", "claude-code", "tool.post"), - ("UserPromptSubmit", "claude-code", "prompt.submitted"), + ( + "UserPromptSubmit", + "claude-code", + "session.prompt-submitted", + ), ("Stop", "codex", "session.stopped"), ]; diff --git a/src/render/default.rs b/src/render/default.rs index 1d1155b..2052082 100644 --- a/src/render/default.rs +++ b/src/render/default.rs @@ -414,13 +414,18 @@ fn session_subject(payload: &Value) -> String { fn session_status_label(kind: &str, payload: &Value) -> String { match kind { - "session.started" | "session.blocked" | "session.finished" | "session.failed" => { - optional_string_field(payload, "status").unwrap_or_else(|| { - kind.strip_prefix("session.") - .unwrap_or(kind) - .replace('-', " ") - }) - } + "session.started" + | "session.blocked" + | "session.finished" + | "session.failed" + | "session.prompt-submitted" + | "session.prompt-delivered" + | "session.prompt-delivery-failed" + | "session.stopped" => optional_string_field(payload, "status").unwrap_or_else(|| { + kind.strip_prefix("session.") + .unwrap_or(kind) + .replace('-', " ") + }), _ => kind.strip_prefix("session.").unwrap_or(kind).to_string(), } }