返回 CodeWhale
task.rs
根目录 / crates / tui / src / commands / groups / utility / task.rs
1 //! Task commands: add/list/show/cancel
2
3 use crate::commands::traits::{CommandInfo, RegisterCommand};
4 use crate::localization::MessageId;
5 use crate::tui::app::{App, AppAction};
6
7 use crate::commands::CommandResult;
8
9 pub(in crate::commands) const COMMAND_INFO: CommandInfo = CommandInfo {
10 name: "task",
11 aliases: &["tasks"],
12 usage: "/task [add <prompt>|list|digest|show <id>|cancel <id>]",
13 description_id: MessageId::CmdTaskDescription,
14 };
15
16 pub(in crate::commands) struct TaskCmd;
17
18 impl RegisterCommand for TaskCmd {
19 fn info() -> &'static CommandInfo {
20 &COMMAND_INFO
21 }
22
23 fn execute(app: &mut App, arg: Option<&str>) -> CommandResult {
24 task(app, arg)
25 }
26 }
27
28 fn task(app: &mut App, args: Option<&str>) -> CommandResult {
29 let raw = args.unwrap_or("").trim();
30 if raw.is_empty() || raw.eq_ignore_ascii_case("list") {
31 return CommandResult::action(AppAction::TaskList);
32 }
33
34 let mut parts = raw.splitn(2, char::is_whitespace);
35 let action = parts.next().unwrap_or("").to_ascii_lowercase();
36 let remainder = parts.next().map(str::trim).filter(|s| !s.is_empty());
37
38 match action.as_str() {
39 "add" => {
40 let Some(prompt) = remainder else {
41 return CommandResult::error("Usage: /task add <prompt>");
42 };
43 CommandResult::action(AppAction::TaskAdd {
44 prompt: prompt.to_string(),
45 })
46 }
47 "list" => CommandResult::action(AppAction::TaskList),
48 "digest" => {
49 let Some(work) = app.runtime_services.work.as_ref() else {
50 return CommandResult::message("No active operations or to-do items.");
51 };
52 match work.capture(app.current_session_id.as_deref()) {
53 Ok(snapshot) => CommandResult::message(crate::work_graph::format_operation_digest(
54 snapshot.as_ref(),
55 )),
56 Err(error) => CommandResult::error(format!(
57 "Operation digest is temporarily unavailable: {error}"
58 )),
59 }
60 }
61 "show" => {
62 let Some(id) = remainder else {
63 return CommandResult::error("Usage: /task show <id>");
64 };
65 CommandResult::action(AppAction::TaskShow { id: id.to_string() })
66 }
67 "cancel" | "stop" => {
68 let Some(id) = remainder else {
69 return CommandResult::error("Usage: /task cancel <id>");
70 };
71 CommandResult::action(AppAction::TaskCancel { id: id.to_string() })
72 }
73 _ => CommandResult::error("Usage: /task [add <prompt>|list|digest|show <id>|cancel <id>]"),
74 }
75 }
76
77 #[cfg(test)]
78 mod tests {
79 use super::*;
80 use crate::config::Config;
81 use crate::tui::app::TuiOptions;
82 use std::path::PathBuf;
83
84 fn app() -> App {
85 App::new(
86 TuiOptions {
87 use_alt_screen: false,
88 max_subagents: 2,
89 ..crate::test_support::test_tui_options(PathBuf::from("."))
90 },
91 &Config::default(),
92 )
93 }
94
95 #[test]
96 fn parses_add_and_cancel() {
97 let mut app = app();
98 let add = task(&mut app, Some("add write tests"));
99 assert!(matches!(
100 add.action,
101 Some(AppAction::TaskAdd { prompt }) if prompt == "write tests"
102 ));
103
104 let cancel = task(&mut app, Some("cancel task_1234"));
105 assert!(matches!(
106 cancel.action,
107 Some(AppAction::TaskCancel { id }) if id == "task_1234"
108 ));
109 }
110
111 #[test]
112 fn validates_usage() {
113 let mut app = app();
114 let result = task(&mut app, Some("add"));
115 assert!(result.message.is_some());
116 assert!(result.action.is_none());
117 }
118
119 #[test]
120 fn digest_uses_canonical_work_runtime_without_another_state_store() {
121 let mut app = app();
122 let result = task(&mut app, Some("digest"));
123 assert_eq!(
124 result.message.as_deref(),
125 Some("No active operations or to-do items.")
126 );
127 assert!(result.action.is_none());
128 }
129 }
130
130 lines RUST