Skip to content

Commit 82fb2d7

Browse files
authored
fix: make todo lists an experimental feature (#2740)
1 parent e97878f commit 82fb2d7

File tree

7 files changed

+67
-16
lines changed

7 files changed

+67
-16
lines changed

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use crossterm::{
1212
};
1313
use dialoguer::Select;
1414

15+
use crate::cli::chat::conversation::format_tool_spec;
1516
use crate::cli::chat::{
1617
ChatError,
1718
ChatSession,
@@ -44,6 +45,11 @@ static AVAILABLE_EXPERIMENTS: &[Experiment] = &[
4445
description: "Enables entering into a temporary mode for sending isolated conversations (/tangent)",
4546
setting_key: Setting::EnabledTangentMode,
4647
},
48+
Experiment {
49+
name: "Todo Lists",
50+
description: "Enables Q to create todo lists that can be viewed and managed using /todos",
51+
setting_key: Setting::EnabledTodoList,
52+
},
4753
];
4854

4955
#[derive(Debug, PartialEq, Args)]
@@ -135,11 +141,14 @@ async fn select_experiment(os: &mut Os, session: &mut ChatSession) -> Result<Opt
135141
.map_err(|e| ChatError::Custom(format!("Failed to update experiment setting: {e}").into()))?;
136142

137143
// Reload tools to reflect the experiment change
138-
let _ = session
144+
let tools = session
139145
.conversation
140146
.tool_manager
141147
.load_tools(os, &mut session.stderr)
142-
.await;
148+
.await
149+
.map_err(|e| ChatError::Custom(format!("Failed to update tool spec: {e}").into()))?;
150+
151+
session.conversation.tools = format_tool_spec(tools);
143152

144153
let status_text = if new_state { "enabled" } else { "disabled" };
145154

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use dialoguer::Select;
88
use eyre::Result;
99

1010
use crate::cli::chat::tools::todo::{
11+
TodoList,
1112
TodoListState,
1213
delete_todo,
1314
get_all_todos,
@@ -65,6 +66,18 @@ impl std::fmt::Display for TodoDisplayEntry {
6566

6667
impl TodoSubcommand {
6768
pub async fn execute(self, os: &mut Os, session: &mut ChatSession) -> Result<ChatState, ChatError> {
69+
// Check if todo lists are enabled
70+
if !TodoList::is_enabled(os) {
71+
execute!(
72+
session.stderr,
73+
style::SetForegroundColor(style::Color::Red),
74+
style::Print("Todo lists are disabled. Enable them with: q settings chat.enableTodoList true\n"),
75+
style::SetForegroundColor(style::Color::Reset)
76+
)?;
77+
return Ok(ChatState::PromptUser {
78+
skip_printing_tools: true,
79+
});
80+
}
6881
TodoListState::init_dir(os)
6982
.await
7083
.map_err(|e| ChatError::Custom(format!("Could not create todos directory: {e}").into()))?;

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

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -188,19 +188,7 @@ impl ConversationState {
188188
history: VecDeque::new(),
189189
valid_history_range: Default::default(),
190190
transcript: VecDeque::with_capacity(MAX_CONVERSATION_STATE_HISTORY_LEN),
191-
tools: tool_config
192-
.into_values()
193-
.fold(HashMap::<ToolOrigin, Vec<Tool>>::new(), |mut acc, v| {
194-
let tool = Tool::ToolSpecification(ToolSpecification {
195-
name: v.name,
196-
description: v.description,
197-
input_schema: v.input_schema.into(),
198-
});
199-
acc.entry(v.tool_origin)
200-
.and_modify(|tools| tools.push(tool.clone()))
201-
.or_insert(vec![tool]);
202-
acc
203-
}),
191+
tools: format_tool_spec(tool_config),
204192
context_manager,
205193
tool_manager,
206194
context_message_length: None,
@@ -854,6 +842,22 @@ Return only the JSON configuration, no additional text.",
854842
}
855843
}
856844

845+
pub fn format_tool_spec(tool_spec: HashMap<String, ToolSpec>) -> HashMap<ToolOrigin, Vec<Tool>> {
846+
tool_spec
847+
.into_values()
848+
.fold(HashMap::<ToolOrigin, Vec<Tool>>::new(), |mut acc, v| {
849+
let tool = Tool::ToolSpecification(ToolSpecification {
850+
name: v.name,
851+
description: v.description,
852+
input_schema: v.input_schema.into(),
853+
});
854+
acc.entry(v.tool_origin)
855+
.and_modify(|tools| tools.push(tool.clone()))
856+
.or_insert(vec![tool]);
857+
acc
858+
})
859+
}
860+
857861
/// Represents a conversation state that can be converted into a [FigConversationState] (the type
858862
/// used by the API client). Represents borrowed data, and reflects an exact [FigConversationState]
859863
/// that can be generated from [ConversationState] at any point in time.

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,9 @@ impl ToolManager {
650650
if !crate::cli::chat::tools::knowledge::Knowledge::is_enabled(os) {
651651
tool_specs.remove("knowledge");
652652
}
653+
if !crate::cli::chat::tools::todo::TodoList::is_enabled(os) {
654+
tool_specs.remove("todo_list");
655+
}
653656

654657
#[cfg(windows)]
655658
{

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ use crate::cli::chat::line_tracker::FileLineTracker;
5858
use crate::os::Os;
5959

6060
pub const DEFAULT_APPROVE: [&str; 1] = ["fs_read"];
61-
pub const NATIVE_TOOLS: [&str; 7] = [
61+
pub const NATIVE_TOOLS: [&str; 8] = [
6262
"fs_read",
6363
"fs_write",
6464
#[cfg(windows)]
@@ -69,6 +69,7 @@ pub const NATIVE_TOOLS: [&str; 7] = [
6969
"gh_issue",
7070
"knowledge",
7171
"thinking",
72+
"todo_list",
7273
];
7374

7475
/// Represents an executable tool use.

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use serde::{
2424
};
2525

2626
use super::InvokeOutput;
27+
use crate::database::settings::Setting;
2728
use crate::os::Os;
2829

2930
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
@@ -213,7 +214,23 @@ pub enum TodoList {
213214
}
214215

215216
impl TodoList {
217+
/// Checks if todo lists are enabled
218+
pub fn is_enabled(os: &Os) -> bool {
219+
os.database.settings.get_bool(Setting::EnabledTodoList).unwrap_or(false)
220+
}
221+
216222
pub async fn invoke(&self, os: &Os, output: &mut impl Write) -> Result<InvokeOutput> {
223+
if !Self::is_enabled(os) {
224+
queue!(
225+
output,
226+
style::SetForegroundColor(style::Color::Red),
227+
style::Print("Todo lists are disabled. Enable them with: q settings chat.enableTodoList true"),
228+
style::SetForegroundColor(style::Color::Reset)
229+
)?;
230+
return Ok(InvokeOutput {
231+
output: super::OutputKind::Text("Todo lists are disabled.".to_string()),
232+
});
233+
}
217234
if let Some(id) = self.get_id() {
218235
if !os.fs.exists(id_to_path(os, &id)?) {
219236
let error_string = "No todo list exists with the given ID";

crates/chat-cli/src/database/settings.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ pub enum Setting {
7575
ChatDisableAutoCompaction,
7676
#[strum(message = "Show conversation history hints (boolean)")]
7777
ChatEnableHistoryHints,
78+
#[strum(message = "Enable the todo list feature (boolean)")]
79+
EnabledTodoList,
7880
}
7981

8082
impl AsRef<str> for Setting {
@@ -109,6 +111,7 @@ impl AsRef<str> for Setting {
109111
Self::ChatDefaultAgent => "chat.defaultAgent",
110112
Self::ChatDisableAutoCompaction => "chat.disableAutoCompaction",
111113
Self::ChatEnableHistoryHints => "chat.enableHistoryHints",
114+
Self::EnabledTodoList => "chat.enableTodoList",
112115
}
113116
}
114117
}
@@ -153,6 +156,7 @@ impl TryFrom<&str> for Setting {
153156
"chat.defaultAgent" => Ok(Self::ChatDefaultAgent),
154157
"chat.disableAutoCompaction" => Ok(Self::ChatDisableAutoCompaction),
155158
"chat.enableHistoryHints" => Ok(Self::ChatEnableHistoryHints),
159+
"chat.enableTodoList" => Ok(Self::EnabledTodoList),
156160
_ => Err(DatabaseError::InvalidSetting(value.to_string())),
157161
}
158162
}

0 commit comments

Comments
 (0)