Skip to content

Commit da54512

Browse files
author
kiran-garre
committed
feat: Add basic persistence functionality to todo_list
Updates: - Users can select past to-do lists to complete - Q will load the selected to-do list and finish the remaining tasks Future changes - Improve consistency of todo_list tool - Add more checks for argument validity - Add more detailed context to todo_lists for cross-session usage
1 parent 30fc467 commit da54512

File tree

6 files changed

+194
-137
lines changed

6 files changed

+194
-137
lines changed

crates/chat-cli/src/cli/chat/cli/todo.rs

Lines changed: 0 additions & 44 deletions
This file was deleted.

crates/chat-cli/src/cli/chat/cli/todos.rs

Lines changed: 79 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,15 @@ use eyre::{
1919
bail,
2020
};
2121

22+
use crate::cli::chat::tools::todo::{
23+
build_path,
24+
TODO_STATE_FOLDER_PATH,
25+
};
26+
2227
use dialoguer::{
2328
FuzzySelect
2429
};
2530

26-
const TODO_INTERNAL_PROMPT: &str = "This is the internal prompt that will be
27-
sent to create the todo list";
28-
29-
// ########### REMOVE BEFORE PUSHING ##############
30-
const TODO_STATE_FOLDER_PATH: &str = "/Users/kiranbug/.aws/amazonq/todos"; // temporary path where we store state files
31-
// ########### --------------------- ##############
32-
33-
3431
#[derive(Debug, PartialEq, Subcommand)]
3532
pub enum TodoSubcommand {
3633
// Task/prompt to generate TODO list for
@@ -66,16 +63,16 @@ impl std::fmt::Display for TodoDisplayEntry {
6663

6764
impl TodoSubcommand {
6865
pub async fn execute(self, os: &mut Os, session: &mut ChatSession) -> Result<ChatState, ChatError> {
69-
70-
execute!(
71-
session.stderr,
72-
style::Print("Dummy? Who you callin' a dummy?\n")
73-
)?;
74-
7566
match self {
7667
Self::Show => {
7768
match self.get_descriptions_and_statuses(os).await {
7869
Ok(entries) => {
70+
if entries.len() == 0 {
71+
execute!(
72+
session.stderr,
73+
style::Print("No to-do lists to show"),
74+
);
75+
}
7976
for e in entries {
8077
execute!(
8178
session.stderr,
@@ -98,11 +95,30 @@ impl TodoSubcommand {
9895
Self::Select => {
9996
match self.get_descriptions_and_statuses(os).await {
10097
Ok(entries) => {
101-
let selection_index = FuzzySelect::new()
102-
.items(&entries)
103-
.interact()
104-
.unwrap(); // FIX THIS
105-
return session.resume_todo(os, entries[selection_index].path.clone()).await;
98+
if entries.len() == 0 {
99+
execute!(
100+
session.stderr,
101+
style::Print("No to-do lists to show"),
102+
);
103+
} else {
104+
let selection = FuzzySelect::new()
105+
.with_prompt("Select task:")
106+
.items(&entries)
107+
.report(false)
108+
.interact_opt()
109+
.unwrap_or(None); // FIX: workaround for ^C during selection
110+
111+
if let Some(index) = selection {
112+
if index < entries.len() {
113+
execute!(
114+
session.stderr,
115+
style::Print("⟳ Resuming: ".magenta()),
116+
style::Print(format!("{}\n", entries[index].description.clone())),
117+
);
118+
return session.resume_todo(os, entries[index].path.clone()).await;
119+
}
120+
}
121+
}
106122
}
107123
Err(e) => println!("{:?}", e),
108124
};
@@ -113,22 +129,63 @@ impl TodoSubcommand {
113129

114130
async fn get_descriptions_and_statuses(self, os: &Os) -> Result<Vec<TodoDisplayEntry>> {
115131
let mut out = Vec::new();
116-
let mut entries = os.fs.read_dir(TODO_STATE_FOLDER_PATH).await?;
132+
let mut entries = os.fs.read_dir(
133+
build_path(os, TODO_STATE_FOLDER_PATH, "")?
134+
).await?;
117135

118136
while let Some(entry) = entries.next_entry().await? {
119137
let contents = os.fs.read_to_string(entry.path()).await?;
120138
let temp_struct = match serde_json::from_str::<TodoState>(&contents) {
121139
Ok(state) => state,
122140
Err(_) => continue,
123141
};
124-
out.push(TodoDisplayEntry {
142+
out.push( TodoDisplayEntry {
125143
num_completed: temp_struct.completed.iter().filter(|b| **b).count(),
126144
num_tasks: temp_struct.completed.len(),
127-
description: temp_struct.task_description,
145+
description: prewrap(&temp_struct.task_description),
128146
path: entry.path(),
129147
});
130148
}
131149
Ok(out)
132150
}
133151

152+
}
153+
154+
155+
const MAX_LINE_LENGTH: usize = 80;
156+
157+
// FIX: Hacky workaround for cleanly wrapping lines
158+
/// Insert newlines every n characters, not within a word and not at the end.
159+
///
160+
/// Generated by Q
161+
fn prewrap(text: &str) -> String {
162+
if text.is_empty() || MAX_LINE_LENGTH == 0 {
163+
return text.to_string();
164+
}
165+
166+
let mut result = String::new();
167+
let mut current_line_length = 0;
168+
let words: Vec<&str> = text.split_whitespace().collect();
169+
170+
for (i, word) in words.iter().enumerate() {
171+
let word_length = word.len();
172+
173+
// If adding this word would exceed the line length and we're not at the start of a line
174+
if current_line_length > 0 && current_line_length + 1 + word_length > MAX_LINE_LENGTH {
175+
result.push('\n');
176+
result.push_str(&" ".repeat("> ".len()));
177+
current_line_length = 0;
178+
}
179+
180+
// Add space before word if not at start of line
181+
if current_line_length > 0 {
182+
result.push(' ');
183+
current_line_length += 1;
184+
}
185+
186+
result.push_str(word);
187+
current_line_length += word_length;
188+
}
189+
190+
result
134191
}

crates/chat-cli/src/cli/chat/conversation.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -852,7 +852,8 @@ impl ConversationState {
852852
let request = format!(
853853
"[SYSTEM NOTE: This is an automated request, not from the user]
854854
Read the TODO list contents below and understand the task description, completed tasks, and provided context.
855-
Call the Load command of the todo_list tool with the given file path as an argument to display the TODO list to the user and officially resume execution of the TODO list tasks.
855+
Call the `load` command of the todo_list tool with the given file path as an argument to display the TODO list to the user and officially resume execution of the TODO list tasks.
856+
You do not need to display the tasks to the user yourself. You can begin completing the tasks after calling the `load` command.
856857
TODO LIST CONTENTS: {}
857858
FILE PATH: {}",
858859
contents,

crates/chat-cli/src/cli/chat/mod.rs

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -977,13 +977,15 @@ impl ChatSession {
977977
os: &mut Os,
978978
path: PathBuf,
979979
) -> Result<ChatState, ChatError> {
980+
980981
let request_state = self
981982
.conversation
982983
.create_todo_request(os, path)
983984
.await;
984985

985986
match request_state {
986987
Ok(state) => {
988+
self.conversation.reset_next_user_message();
987989
self.conversation.set_next_user_message(state.user_input_message.content).await;
988990
},
989991
Err(_) => {
@@ -1006,23 +1008,22 @@ impl ChatSession {
10061008
let response = os.client.send_message(conv_state).await?;
10071009

10081010
let mut result = self.handle_response(os, response).await?;
1009-
match result {
1010-
ChatState::ExecuteTools => {
1011-
result = self.tool_use_execute(os).await?;
1012-
},
1013-
ChatState::ValidateTools(tool_uses) => {
1014-
result = self.validate_tools(os, tool_uses).await?;
1015-
},
1016-
ChatState::HandleResponseStream(response) => {
1017-
result = self.handle_response(os, response).await?;
1018-
},
1019-
other => {
1020-
return Ok(other);
1021-
},
1022-
}
1023-
Ok(result)
1024-
1025-
1011+
loop {
1012+
match result {
1013+
ChatState::ExecuteTools => {
1014+
result = self.tool_use_execute(os).await?;
1015+
},
1016+
ChatState::ValidateTools(tool_uses) => {
1017+
result = self.validate_tools(os, tool_uses).await?;
1018+
},
1019+
ChatState::HandleResponseStream(response) => {
1020+
result = self.handle_response(os, response).await?;
1021+
},
1022+
other => {
1023+
return Ok(other);
1024+
},
1025+
}
1026+
};
10261027
// Ok(ChatState::PromptUser {
10271028
// skip_printing_tools: true,
10281029
// })

0 commit comments

Comments
 (0)