返回 CodeWhale
agent.rs
根目录 / crates / tui / src / commands / groups / core / agent.rs
1 //! `/agent` command.
2
3 use crate::commands::traits::{CommandInfo, RegisterCommand};
4 use crate::localization::MessageId;
5 use crate::tui::app::{App, AppAction};
6
7 use super::CommandResult;
8
9 pub(in crate::commands) const COMMAND_INFO: CommandInfo = CommandInfo {
10 name: "agent",
11 aliases: &["daili"],
12 usage: "/agent [N] <task>",
13 description_id: MessageId::CmdAgentDescription,
14 };
15
16 pub(in crate::commands) struct AgentCmd;
17
18 impl RegisterCommand for AgentCmd {
19 fn info() -> &'static CommandInfo {
20 &COMMAND_INFO
21 }
22
23 fn execute(app: &mut App, arg: Option<&str>) -> CommandResult {
24 agent(app, arg)
25 }
26 }
27
28 pub fn agent(_app: &mut App, arg: Option<&str>) -> CommandResult {
29 if let Some(action) = parse_agent_control_action(arg) {
30 if action.action == "cancel" {
31 return CommandResult::with_message_and_action(
32 format!("Cancelling agent {}...", action.agent_id),
33 AppAction::CancelSubAgent {
34 agent_id: action.agent_id,
35 },
36 );
37 }
38 let message = format!(
39 "Call `agent` with action `{}`, agent_id `{}`, then summarize the returned status for the user. Do not start a new agent.",
40 action.action, action.agent_id
41 );
42 return CommandResult::with_message_and_action(
43 format!("Agent {} requested for {}.", action.action, action.agent_id),
44 AppAction::SendMessage(message),
45 );
46 }
47
48 let (max_depth, task) = match super::util::parse_depth_prefixed_arg(arg, 1) {
49 Ok(parsed) => parsed,
50 Err(message) => return CommandResult::error(message),
51 };
52 let task = match task {
53 Some(task) if !task.trim().is_empty() => task.trim().to_string(),
54 _ => {
55 return CommandResult::error(
56 "Usage: /agent [N] <task>\n\n\
57 Opens a persistent sub-agent session with recursive agent depth N (0-3, default 1).",
58 );
59 }
60 };
61 let message = format!(
62 "Launch one sub-agent for this task by calling `agent` with name `slash_agent`, `prompt: {task:?}`, and `max_depth: {max_depth}`. Use `handle_read` on the returned transcript_handle if you need more detail. Verify any claimed side effects before reporting success."
63 );
64 CommandResult::with_message_and_action(
65 format!("Opening persistent sub-agent at depth {max_depth}..."),
66 AppAction::SendMessage(message),
67 )
68 }
69
70 struct AgentControlAction {
71 action: &'static str,
72 agent_id: String,
73 }
74
75 fn parse_agent_control_action(arg: Option<&str>) -> Option<AgentControlAction> {
76 let arg = arg?.trim();
77 let (action, rest) = arg.split_once(char::is_whitespace)?;
78 let action = match action {
79 "status" | "inspect" => "status",
80 "peek" | "progress" => "peek",
81 "cancel" | "stop" | "abort" => "cancel",
82 _ => return None,
83 };
84 let agent_id = rest.trim();
85 if agent_id.is_empty() || agent_id.contains(char::is_whitespace) {
86 return None;
87 }
88 Some(AgentControlAction {
89 action,
90 agent_id: agent_id.to_string(),
91 })
92 }
93
94 #[cfg(test)]
95 mod tests {
96 use super::*;
97 use std::path::PathBuf;
98
99 use crate::tui::app::TuiOptions;
100
101 fn test_app() -> App {
102 let options = TuiOptions {
103 ..crate::test_support::test_tui_options(PathBuf::from("."))
104 };
105 App::new(options, &crate::config::Config::default())
106 }
107
108 #[test]
109 fn agent_control_actions_route_to_existing_agent_tool() {
110 let mut app = test_app();
111 let result = agent(&mut app, Some("peek agent_123"));
112
113 assert!(!result.is_error);
114 let Some(AppAction::SendMessage(message)) = result.action else {
115 panic!("expected SendMessage action");
116 };
117 assert!(message.contains("action `peek`"));
118 assert!(message.contains("agent_id `agent_123`"));
119 assert!(message.contains("Do not start a new agent"));
120
121 let result = agent(&mut app, Some("cancel agent_123"));
122 let Some(AppAction::CancelSubAgent { agent_id }) = result.action else {
123 panic!("expected CancelSubAgent action");
124 };
125 assert_eq!(agent_id, "agent_123");
126 }
127 }
128
128 lines RUST