diff --git a/codex-rs/core/src/tools/handlers/mod.rs b/codex-rs/core/src/tools/handlers/mod.rs index 9f4a1a16ce2..b0e1b3c0aa7 100644 --- a/codex-rs/core/src/tools/handlers/mod.rs +++ b/codex-rs/core/src/tools/handlers/mod.rs @@ -1,11 +1,11 @@ pub mod apply_patch; -pub(crate) mod collab; mod dynamic; mod grep_files; mod js_repl; mod list_dir; mod mcp; mod mcp_resource; +pub(crate) mod multi_agents; mod plan; mod read_file; mod request_user_input; @@ -20,7 +20,6 @@ use serde::Deserialize; use crate::function_tool::FunctionCallError; pub use apply_patch::ApplyPatchHandler; -pub use collab::CollabHandler; pub use dynamic::DynamicToolHandler; pub use grep_files::GrepFilesHandler; pub use js_repl::JsReplHandler; @@ -28,6 +27,7 @@ pub use js_repl::JsReplResetHandler; pub use list_dir::ListDirHandler; pub use mcp::McpHandler; pub use mcp_resource::McpResourceHandler; +pub use multi_agents::MultiAgentHandler; pub use plan::PlanHandler; pub use read_file::ReadFileHandler; pub use request_user_input::RequestUserInputHandler; diff --git a/codex-rs/core/src/tools/handlers/collab.rs b/codex-rs/core/src/tools/handlers/multi_agents.rs similarity index 97% rename from codex-rs/core/src/tools/handlers/collab.rs rename to codex-rs/core/src/tools/handlers/multi_agents.rs index 8235b291e87..c5a0f59ae31 100644 --- a/codex-rs/core/src/tools/handlers/collab.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents.rs @@ -34,7 +34,7 @@ use codex_protocol::user_input::UserInput; use serde::Deserialize; use serde::Serialize; -pub struct CollabHandler; +pub struct MultiAgentHandler; /// Minimum wait timeout to prevent tight polling loops from burning CPU. pub(crate) const MIN_WAIT_TIMEOUT_MS: i64 = 10_000; @@ -47,7 +47,7 @@ struct CloseAgentArgs { } #[async_trait] -impl ToolHandler for CollabHandler { +impl ToolHandler for MultiAgentHandler { fn kind(&self) -> ToolKind { ToolKind::Function } @@ -917,7 +917,7 @@ mod tests { input: "hello".to_string(), }, ); - let Err(err) = CollabHandler.handle(invocation).await else { + let Err(err) = MultiAgentHandler.handle(invocation).await else { panic!("payload should be rejected"); }; assert_eq!( @@ -937,7 +937,7 @@ mod tests { "unknown_tool", function_payload(json!({})), ); - let Err(err) = CollabHandler.handle(invocation).await else { + let Err(err) = MultiAgentHandler.handle(invocation).await else { panic!("tool should be rejected"); }; assert_eq!( @@ -955,7 +955,7 @@ mod tests { "spawn_agent", function_payload(json!({"message": " "})), ); - let Err(err) = CollabHandler.handle(invocation).await else { + let Err(err) = MultiAgentHandler.handle(invocation).await else { panic!("empty message should be rejected"); }; assert_eq!( @@ -978,7 +978,7 @@ mod tests { "items": [{"type": "mention", "name": "drive", "path": "app://drive"}] })), ); - let Err(err) = CollabHandler.handle(invocation).await else { + let Err(err) = MultiAgentHandler.handle(invocation).await else { panic!("message+items should be rejected"); }; assert_eq!( @@ -1016,7 +1016,7 @@ mod tests { "agent_type": "explorer" })), ); - let output = CollabHandler + let output = MultiAgentHandler .handle(invocation) .await .expect("spawn_agent should succeed"); @@ -1048,7 +1048,7 @@ mod tests { "spawn_agent", function_payload(json!({"message": "hello"})), ); - let Err(err) = CollabHandler.handle(invocation).await else { + let Err(err) = MultiAgentHandler.handle(invocation).await else { panic!("spawn should fail without a manager"); }; assert_eq!( @@ -1074,7 +1074,7 @@ mod tests { "spawn_agent", function_payload(json!({"message": "hello"})), ); - let Err(err) = CollabHandler.handle(invocation).await else { + let Err(err) = MultiAgentHandler.handle(invocation).await else { panic!("spawn should fail when depth limit exceeded"); }; assert_eq!( @@ -1094,7 +1094,7 @@ mod tests { "send_input", function_payload(json!({"id": ThreadId::new().to_string(), "message": ""})), ); - let Err(err) = CollabHandler.handle(invocation).await else { + let Err(err) = MultiAgentHandler.handle(invocation).await else { panic!("empty message should be rejected"); }; assert_eq!( @@ -1118,7 +1118,7 @@ mod tests { "items": [{"type": "mention", "name": "drive", "path": "app://drive"}] })), ); - let Err(err) = CollabHandler.handle(invocation).await else { + let Err(err) = MultiAgentHandler.handle(invocation).await else { panic!("message+items should be rejected"); }; assert_eq!( @@ -1138,7 +1138,7 @@ mod tests { "send_input", function_payload(json!({"id": "not-a-uuid", "message": "hi"})), ); - let Err(err) = CollabHandler.handle(invocation).await else { + let Err(err) = MultiAgentHandler.handle(invocation).await else { panic!("invalid id should be rejected"); }; let FunctionCallError::RespondToModel(msg) = err else { @@ -1159,7 +1159,7 @@ mod tests { "send_input", function_payload(json!({"id": agent_id.to_string(), "message": "hi"})), ); - let Err(err) = CollabHandler.handle(invocation).await else { + let Err(err) = MultiAgentHandler.handle(invocation).await else { panic!("missing agent should be reported"); }; assert_eq!( @@ -1186,7 +1186,7 @@ mod tests { "interrupt": true })), ); - CollabHandler + MultiAgentHandler .handle(invocation) .await .expect("send_input should succeed"); @@ -1227,7 +1227,7 @@ mod tests { ] })), ); - CollabHandler + MultiAgentHandler .handle(invocation) .await .expect("send_input should succeed"); @@ -1267,7 +1267,7 @@ mod tests { "resume_agent", function_payload(json!({"id": "not-a-uuid"})), ); - let Err(err) = CollabHandler.handle(invocation).await else { + let Err(err) = MultiAgentHandler.handle(invocation).await else { panic!("invalid id should be rejected"); }; let FunctionCallError::RespondToModel(msg) = err else { @@ -1288,7 +1288,7 @@ mod tests { "resume_agent", function_payload(json!({"id": agent_id.to_string()})), ); - let Err(err) = CollabHandler.handle(invocation).await else { + let Err(err) = MultiAgentHandler.handle(invocation).await else { panic!("missing agent should be reported"); }; assert_eq!( @@ -1313,7 +1313,7 @@ mod tests { function_payload(json!({"id": agent_id.to_string()})), ); - let output = CollabHandler + let output = MultiAgentHandler .handle(invocation) .await .expect("resume_agent should succeed"); @@ -1382,7 +1382,7 @@ mod tests { "resume_agent", function_payload(json!({"id": agent_id.to_string()})), ); - let output = CollabHandler + let output = MultiAgentHandler .handle(resume_invocation) .await .expect("resume_agent should succeed"); @@ -1405,7 +1405,7 @@ mod tests { "send_input", function_payload(json!({"id": agent_id.to_string(), "message": "hello"})), ); - let output = CollabHandler + let output = MultiAgentHandler .handle(send_invocation) .await .expect("send_input should succeed after resume"); @@ -1450,7 +1450,7 @@ mod tests { "resume_agent", function_payload(json!({"id": ThreadId::new().to_string()})), ); - let Err(err) = CollabHandler.handle(invocation).await else { + let Err(err) = MultiAgentHandler.handle(invocation).await else { panic!("resume should fail when depth limit exceeded"); }; assert_eq!( @@ -1479,7 +1479,7 @@ mod tests { "timeout_ms": 0 })), ); - let Err(err) = CollabHandler.handle(invocation).await else { + let Err(err) = MultiAgentHandler.handle(invocation).await else { panic!("non-positive timeout should be rejected"); }; assert_eq!( @@ -1497,7 +1497,7 @@ mod tests { "wait", function_payload(json!({"ids": ["invalid"]})), ); - let Err(err) = CollabHandler.handle(invocation).await else { + let Err(err) = MultiAgentHandler.handle(invocation).await else { panic!("invalid id should be rejected"); }; let FunctionCallError::RespondToModel(msg) = err else { @@ -1515,7 +1515,7 @@ mod tests { "wait", function_payload(json!({"ids": []})), ); - let Err(err) = CollabHandler.handle(invocation).await else { + let Err(err) = MultiAgentHandler.handle(invocation).await else { panic!("empty ids should be rejected"); }; assert_eq!( @@ -1540,7 +1540,7 @@ mod tests { "timeout_ms": 1000 })), ); - let output = CollabHandler + let output = MultiAgentHandler .handle(invocation) .await .expect("wait should succeed"); @@ -1584,7 +1584,7 @@ mod tests { "timeout_ms": MIN_WAIT_TIMEOUT_MS })), ); - let output = CollabHandler + let output = MultiAgentHandler .handle(invocation) .await .expect("wait should succeed"); @@ -1632,7 +1632,11 @@ mod tests { })), ); - let early = timeout(Duration::from_millis(50), CollabHandler.handle(invocation)).await; + let early = timeout( + Duration::from_millis(50), + MultiAgentHandler.handle(invocation), + ) + .await; assert!( early.is_err(), "wait should not return before the minimum timeout clamp" @@ -1677,7 +1681,7 @@ mod tests { "timeout_ms": 1000 })), ); - let output = CollabHandler + let output = MultiAgentHandler .handle(invocation) .await .expect("wait should succeed"); @@ -1717,7 +1721,7 @@ mod tests { "close_agent", function_payload(json!({"id": agent_id.to_string()})), ); - let output = CollabHandler + let output = MultiAgentHandler .handle(invocation) .await .expect("close_agent should succeed"); diff --git a/codex-rs/core/src/tools/spec.rs b/codex-rs/core/src/tools/spec.rs index 817b7d78775..83af0e52867 100644 --- a/codex-rs/core/src/tools/spec.rs +++ b/codex-rs/core/src/tools/spec.rs @@ -10,9 +10,9 @@ use crate::tools::handlers::SEARCH_TOOL_BM25_DEFAULT_LIMIT; use crate::tools::handlers::SEARCH_TOOL_BM25_TOOL_NAME; use crate::tools::handlers::apply_patch::create_apply_patch_freeform_tool; use crate::tools::handlers::apply_patch::create_apply_patch_json_tool; -use crate::tools::handlers::collab::DEFAULT_WAIT_TIMEOUT_MS; -use crate::tools::handlers::collab::MAX_WAIT_TIMEOUT_MS; -use crate::tools::handlers::collab::MIN_WAIT_TIMEOUT_MS; +use crate::tools::handlers::multi_agents::DEFAULT_WAIT_TIMEOUT_MS; +use crate::tools::handlers::multi_agents::MAX_WAIT_TIMEOUT_MS; +use crate::tools::handlers::multi_agents::MIN_WAIT_TIMEOUT_MS; use crate::tools::handlers::request_user_input_tool_description; use crate::tools::registry::ToolRegistryBuilder; use codex_protocol::config_types::WebSearchMode; @@ -1405,7 +1405,6 @@ pub(crate) fn build_specs( dynamic_tools: &[DynamicToolSpec], ) -> ToolRegistryBuilder { use crate::tools::handlers::ApplyPatchHandler; - use crate::tools::handlers::CollabHandler; use crate::tools::handlers::DynamicToolHandler; use crate::tools::handlers::GrepFilesHandler; use crate::tools::handlers::JsReplHandler; @@ -1413,6 +1412,7 @@ pub(crate) fn build_specs( use crate::tools::handlers::ListDirHandler; use crate::tools::handlers::McpHandler; use crate::tools::handlers::McpResourceHandler; + use crate::tools::handlers::MultiAgentHandler; use crate::tools::handlers::PlanHandler; use crate::tools::handlers::ReadFileHandler; use crate::tools::handlers::RequestUserInputHandler; @@ -1574,17 +1574,17 @@ pub(crate) fn build_specs( builder.register_handler("view_image", view_image_handler); if config.collab_tools { - let collab_handler = Arc::new(CollabHandler); + let multi_agent_handler = Arc::new(MultiAgentHandler); builder.push_spec(create_spawn_agent_tool()); builder.push_spec(create_send_input_tool()); builder.push_spec(create_resume_agent_tool()); builder.push_spec(create_wait_tool()); builder.push_spec(create_close_agent_tool()); - builder.register_handler("spawn_agent", collab_handler.clone()); - builder.register_handler("send_input", collab_handler.clone()); - builder.register_handler("resume_agent", collab_handler.clone()); - builder.register_handler("wait", collab_handler.clone()); - builder.register_handler("close_agent", collab_handler); + builder.register_handler("spawn_agent", multi_agent_handler.clone()); + builder.register_handler("send_input", multi_agent_handler.clone()); + builder.register_handler("resume_agent", multi_agent_handler.clone()); + builder.register_handler("wait", multi_agent_handler.clone()); + builder.register_handler("close_agent", multi_agent_handler); } if let Some(mcp_tools) = mcp_tools { diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index f72255b22ba..dcbee999bc8 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -184,7 +184,6 @@ use crate::bottom_pane::SelectionViewParams; use crate::bottom_pane::custom_prompt_view::CustomPromptView; use crate::bottom_pane::popup_consts::standard_popup_hint_line; use crate::clipboard_paste::paste_image_to_temp_png; -use crate::collab; use crate::collaboration_modes; use crate::diff_render::display_path_for; use crate::exec_cell::CommandOutput; @@ -201,6 +200,7 @@ use crate::history_cell::WebSearchCell; use crate::key_hint; use crate::key_hint::KeyBinding; use crate::markdown::append_markdown; +use crate::multi_agents; use crate::render::Insets; use crate::render::renderable::ColumnRenderable; use crate::render::renderable::FlexRenderable; @@ -4113,17 +4113,19 @@ impl ChatWidget { EventMsg::ExitedReviewMode(review) => self.on_exited_review_mode(review), EventMsg::ContextCompacted(_) => self.on_agent_message("Context compacted".to_owned()), EventMsg::CollabAgentSpawnBegin(_) => {} - EventMsg::CollabAgentSpawnEnd(ev) => self.on_collab_event(collab::spawn_end(ev)), + EventMsg::CollabAgentSpawnEnd(ev) => self.on_collab_event(multi_agents::spawn_end(ev)), EventMsg::CollabAgentInteractionBegin(_) => {} EventMsg::CollabAgentInteractionEnd(ev) => { - self.on_collab_event(collab::interaction_end(ev)) + self.on_collab_event(multi_agents::interaction_end(ev)) } - EventMsg::CollabWaitingBegin(ev) => self.on_collab_event(collab::waiting_begin(ev)), - EventMsg::CollabWaitingEnd(ev) => self.on_collab_event(collab::waiting_end(ev)), + EventMsg::CollabWaitingBegin(ev) => { + self.on_collab_event(multi_agents::waiting_begin(ev)) + } + EventMsg::CollabWaitingEnd(ev) => self.on_collab_event(multi_agents::waiting_end(ev)), EventMsg::CollabCloseBegin(_) => {} - EventMsg::CollabCloseEnd(ev) => self.on_collab_event(collab::close_end(ev)), - EventMsg::CollabResumeBegin(ev) => self.on_collab_event(collab::resume_begin(ev)), - EventMsg::CollabResumeEnd(ev) => self.on_collab_event(collab::resume_end(ev)), + EventMsg::CollabCloseEnd(ev) => self.on_collab_event(multi_agents::close_end(ev)), + EventMsg::CollabResumeBegin(ev) => self.on_collab_event(multi_agents::resume_begin(ev)), + EventMsg::CollabResumeEnd(ev) => self.on_collab_event(multi_agents::resume_end(ev)), EventMsg::ThreadRolledBack(rollback) => { if from_replay { self.app_event_tx.send(AppEvent::ApplyThreadRollback { diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index 639916a79f9..cb4e311576c 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -64,7 +64,6 @@ mod bottom_pane; mod chatwidget; mod cli; mod clipboard_paste; -mod collab; mod collaboration_modes; mod color; pub mod custom_terminal; @@ -86,6 +85,7 @@ mod markdown_render; mod markdown_stream; mod mention_codec; mod model_migration; +mod multi_agents; mod notifications; pub mod onboarding; mod oss_selection; diff --git a/codex-rs/tui/src/collab.rs b/codex-rs/tui/src/multi_agents.rs similarity index 100% rename from codex-rs/tui/src/collab.rs rename to codex-rs/tui/src/multi_agents.rs