diff --git a/app/src/terminal/cli_agent_sessions/listener/mod.rs b/app/src/terminal/cli_agent_sessions/listener/mod.rs index ee2446cd0b..219d4ff88c 100644 --- a/app/src/terminal/cli_agent_sessions/listener/mod.rs +++ b/app/src/terminal/cli_agent_sessions/listener/mod.rs @@ -46,24 +46,27 @@ pub fn is_agent_supported(agent: &CLIAgent) -> bool { | CLIAgent::Codex | CLIAgent::Gemini | CLIAgent::Auggie + | CLIAgent::Pi ) } /// Creates the appropriate handler for the given CLI agent. fn create_handler(agent: &CLIAgent) -> Option> { match agent { - // Auggie is supported via the community-maintained auggie-warp plugin - // (https://github.com/augmentmoogi/auggie-warp), which emits the same + // Auggie and Pi are supported via community-maintained plugins + // (https://github.com/augmentmoogi/auggie-warp, + // https://github.com/badlogic/pi-mono), which emit the same // structured OSC 777 events as the first-party Claude/OpenCode/Gemini - // plugins. We don't ship an install flow for it — we just listen. - CLIAgent::Claude | CLIAgent::OpenCode | CLIAgent::Gemini | CLIAgent::Auggie => { - Some(Box::new(DefaultSessionListener)) - } + // plugins. We don't ship install flows for them — we just listen. + CLIAgent::Claude + | CLIAgent::OpenCode + | CLIAgent::Gemini + | CLIAgent::Auggie + | CLIAgent::Pi => Some(Box::new(DefaultSessionListener)), CLIAgent::Codex => Some(Box::new(CodexSessionHandler)), CLIAgent::Amp | CLIAgent::Droid | CLIAgent::Copilot - | CLIAgent::Pi | CLIAgent::CursorCli | CLIAgent::Goose | CLIAgent::Unknown => None, @@ -287,4 +290,44 @@ mod tests { }; assert!(handler.handle_event(event).is_some()); } + + #[test] + fn pi_is_supported() { + assert!(is_agent_supported(&CLIAgent::Pi)); + } + + #[test] + fn pi_uses_default_handler_with_rich_status() { + assert!(agent_supports_rich_status(&CLIAgent::Pi)); + } + + #[test] + fn pi_default_handler_skips_session_start() { + let mut handler = DefaultSessionListener; + let event = CLIAgentEvent { + v: 1, + agent: CLIAgent::Pi, + event: CLIAgentEventType::SessionStart, + session_id: None, + cwd: None, + project: None, + payload: CLIAgentEventPayload::default(), + }; + assert!(handler.handle_event(event).is_none()); + } + + #[test] + fn pi_default_handler_forwards_stop() { + let mut handler = DefaultSessionListener; + let event = CLIAgentEvent { + v: 1, + agent: CLIAgent::Pi, + event: CLIAgentEventType::Stop, + session_id: None, + cwd: None, + project: None, + payload: CLIAgentEventPayload::default(), + }; + assert!(handler.handle_event(event).is_some()); + } } diff --git a/app/src/terminal/cli_agent_sessions/mod_tests.rs b/app/src/terminal/cli_agent_sessions/mod_tests.rs index 83420536cf..55145a312f 100644 --- a/app/src/terminal/cli_agent_sessions/mod_tests.rs +++ b/app/src/terminal/cli_agent_sessions/mod_tests.rs @@ -222,6 +222,20 @@ fn parse_auggie_stop_notification() { assert_eq!(notif.payload.response.as_deref(), Some("Memory is safe")); } +#[test] +fn parse_pi_stop_notification() { + // Mirrors what the community pi-mono plugin emits on the Stop hook — + // matches the Auggie shape and uses `"agent":"pi"`, which `resolve_agent` + // already maps to `CLIAgent::Pi` via `command_prefix()`. + let body = r#"{"v":1,"agent":"pi","event":"stop","session_id":"abc","cwd":"/tmp/proj","project":"proj","query":"write a haiku","response":"Memory is safe"}"#; + let notif = parse_event(Some("warp://cli-agent"), body).unwrap(); + + assert_eq!(notif.agent, CLIAgent::Pi); + assert_eq!(notif.event, CLIAgentEventType::Stop); + assert_eq!(notif.payload.query.as_deref(), Some("write a haiku")); + assert_eq!(notif.payload.response.as_deref(), Some("Memory is safe")); +} + #[test] fn apply_event_preserves_input_session() { let input_state = CLIAgentInputState::Open {