tunacode-cli 0.0.70__py3-none-any.whl → 0.0.78.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of tunacode-cli might be problematic. Click here for more details.

Files changed (90) hide show
  1. tunacode/cli/commands/__init__.py +0 -2
  2. tunacode/cli/commands/implementations/__init__.py +0 -3
  3. tunacode/cli/commands/implementations/debug.py +2 -2
  4. tunacode/cli/commands/implementations/development.py +10 -8
  5. tunacode/cli/commands/implementations/model.py +357 -29
  6. tunacode/cli/commands/implementations/system.py +3 -2
  7. tunacode/cli/commands/implementations/template.py +0 -2
  8. tunacode/cli/commands/registry.py +8 -7
  9. tunacode/cli/commands/slash/loader.py +2 -1
  10. tunacode/cli/commands/slash/validator.py +2 -1
  11. tunacode/cli/main.py +19 -1
  12. tunacode/cli/repl.py +90 -229
  13. tunacode/cli/repl_components/command_parser.py +2 -1
  14. tunacode/cli/repl_components/error_recovery.py +8 -5
  15. tunacode/cli/repl_components/output_display.py +1 -10
  16. tunacode/cli/repl_components/tool_executor.py +1 -13
  17. tunacode/configuration/defaults.py +2 -2
  18. tunacode/configuration/key_descriptions.py +284 -0
  19. tunacode/configuration/settings.py +0 -1
  20. tunacode/constants.py +6 -42
  21. tunacode/core/agents/__init__.py +43 -2
  22. tunacode/core/agents/agent_components/__init__.py +7 -0
  23. tunacode/core/agents/agent_components/agent_config.py +162 -158
  24. tunacode/core/agents/agent_components/agent_helpers.py +31 -2
  25. tunacode/core/agents/agent_components/node_processor.py +180 -146
  26. tunacode/core/agents/agent_components/response_state.py +123 -6
  27. tunacode/core/agents/agent_components/state_transition.py +116 -0
  28. tunacode/core/agents/agent_components/streaming.py +296 -0
  29. tunacode/core/agents/agent_components/task_completion.py +19 -6
  30. tunacode/core/agents/agent_components/tool_buffer.py +21 -1
  31. tunacode/core/agents/agent_components/tool_executor.py +10 -0
  32. tunacode/core/agents/main.py +522 -370
  33. tunacode/core/agents/main_legact.py +538 -0
  34. tunacode/core/agents/prompts.py +66 -0
  35. tunacode/core/agents/utils.py +29 -122
  36. tunacode/core/setup/__init__.py +0 -2
  37. tunacode/core/setup/config_setup.py +88 -227
  38. tunacode/core/setup/config_wizard.py +230 -0
  39. tunacode/core/setup/coordinator.py +2 -1
  40. tunacode/core/state.py +16 -64
  41. tunacode/core/token_usage/usage_tracker.py +3 -1
  42. tunacode/core/tool_authorization.py +352 -0
  43. tunacode/core/tool_handler.py +67 -60
  44. tunacode/prompts/system.xml +751 -0
  45. tunacode/services/mcp.py +97 -1
  46. tunacode/setup.py +0 -23
  47. tunacode/tools/base.py +54 -1
  48. tunacode/tools/bash.py +14 -0
  49. tunacode/tools/glob.py +4 -2
  50. tunacode/tools/grep.py +7 -17
  51. tunacode/tools/prompts/glob_prompt.xml +1 -1
  52. tunacode/tools/prompts/grep_prompt.xml +1 -0
  53. tunacode/tools/prompts/list_dir_prompt.xml +1 -1
  54. tunacode/tools/prompts/react_prompt.xml +23 -0
  55. tunacode/tools/prompts/read_file_prompt.xml +1 -1
  56. tunacode/tools/react.py +153 -0
  57. tunacode/tools/run_command.py +15 -0
  58. tunacode/types.py +14 -79
  59. tunacode/ui/completers.py +434 -50
  60. tunacode/ui/config_dashboard.py +585 -0
  61. tunacode/ui/console.py +63 -11
  62. tunacode/ui/input.py +8 -3
  63. tunacode/ui/keybindings.py +0 -18
  64. tunacode/ui/model_selector.py +395 -0
  65. tunacode/ui/output.py +40 -19
  66. tunacode/ui/panels.py +173 -49
  67. tunacode/ui/path_heuristics.py +91 -0
  68. tunacode/ui/prompt_manager.py +1 -20
  69. tunacode/ui/tool_ui.py +30 -8
  70. tunacode/utils/api_key_validation.py +93 -0
  71. tunacode/utils/config_comparator.py +340 -0
  72. tunacode/utils/models_registry.py +593 -0
  73. tunacode/utils/text_utils.py +18 -1
  74. {tunacode_cli-0.0.70.dist-info → tunacode_cli-0.0.78.6.dist-info}/METADATA +80 -12
  75. {tunacode_cli-0.0.70.dist-info → tunacode_cli-0.0.78.6.dist-info}/RECORD +78 -74
  76. tunacode/cli/commands/implementations/plan.py +0 -50
  77. tunacode/cli/commands/implementations/todo.py +0 -217
  78. tunacode/context.py +0 -71
  79. tunacode/core/setup/git_safety_setup.py +0 -186
  80. tunacode/prompts/system.md +0 -359
  81. tunacode/prompts/system.md.bak +0 -487
  82. tunacode/tools/exit_plan_mode.py +0 -273
  83. tunacode/tools/present_plan.py +0 -288
  84. tunacode/tools/prompts/exit_plan_mode_prompt.xml +0 -25
  85. tunacode/tools/prompts/present_plan_prompt.xml +0 -20
  86. tunacode/tools/prompts/todo_prompt.xml +0 -96
  87. tunacode/tools/todo.py +0 -456
  88. {tunacode_cli-0.0.70.dist-info → tunacode_cli-0.0.78.6.dist-info}/WHEEL +0 -0
  89. {tunacode_cli-0.0.70.dist-info → tunacode_cli-0.0.78.6.dist-info}/entry_points.txt +0 -0
  90. {tunacode_cli-0.0.70.dist-info → tunacode_cli-0.0.78.6.dist-info}/licenses/LICENSE +0 -0
@@ -1,96 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <tool_prompt>
3
- <description>
4
- Use this tool to create and manage a structured task list for your current coding session. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user.
5
- It also helps the user understand the progress of the task and overall progress of their requests.
6
-
7
- ## When to Use This Tool
8
- Use this tool proactively in these scenarios:
9
-
10
- 1. Complex multi-step tasks - When a task requires 3 or more distinct steps or actions
11
- 2. Non-trivial and complex tasks - Tasks that require careful planning or multiple operations
12
- 3. User explicitly requests todo list - When the user directly asks you to use the todo list
13
- 4. User provides multiple tasks - When users provide a list of things to be done (numbered or comma-separated)
14
- 5. After receiving new instructions - Immediately capture user requirements as todos
15
- 6. When you start working on a task - Mark it as in_progress BEFORE beginning work. Ideally you should only have one todo as in_progress at a time
16
- 7. After completing a task - Mark it as completed and add any new follow-up tasks discovered during implementation
17
-
18
- ## When NOT to Use This Tool
19
-
20
- Skip using this tool when:
21
- 1. There is only a single, straightforward task
22
- 2. The task is trivial and tracking it provides no organizational benefit
23
- 3. The task can be completed in less than 3 trivial steps
24
- 4. The task is purely conversational or informational
25
-
26
- NOTE that you should not use this tool if there is only one trivial task to do. In this case you are better off just doing the task directly.
27
-
28
- ## Task States and Management
29
-
30
- 1. **Task States**: Use these states to track progress:
31
- - pending: Task not yet started
32
- - in_progress: Currently working on (limit to ONE task at a time)
33
- - completed: Task finished successfully
34
-
35
- 2. **Task Management**:
36
- - Update task status in real-time as you work
37
- - Mark tasks complete IMMEDIATELY after finishing (don't batch completions)
38
- - Only have ONE task in_progress at any time
39
- - Complete current tasks before starting new ones
40
- - Remove tasks that are no longer relevant from the list entirely
41
-
42
- 3. **Task Completion Requirements**:
43
- - ONLY mark a task as completed when you have FULLY accomplished it
44
- - If you encounter errors, blockers, or cannot finish, keep the task as in_progress
45
- - When blocked, create a new task describing what needs to be resolved
46
- - Never mark a task as completed if:
47
- - Tests are failing
48
- - Implementation is partial
49
- - You encountered unresolved errors
50
- - You couldn't find necessary files or dependencies
51
-
52
- 4. **Task Breakdown**:
53
- - Create specific, actionable items
54
- - Break complex tasks into smaller, manageable steps
55
- - Use clear, descriptive task names
56
-
57
- When in doubt, use this tool. Being proactive with task management demonstrates attentiveness and ensures you complete all requirements successfully.
58
- </description>
59
-
60
- <parameters>
61
- <parameter name="todos" required="true">
62
- <description>The updated todo list</description>
63
- <type>array</type>
64
- <items>
65
- <type>object</type>
66
- <properties>
67
- <property name="id" required="true">
68
- <type>string</type>
69
- </property>
70
- <property name="content" required="true">
71
- <type>string</type>
72
- <minLength>1</minLength>
73
- </property>
74
- <property name="status" required="true">
75
- <type>string</type>
76
- <enum>pending</enum>
77
- <enum>in_progress</enum>
78
- <enum>completed</enum>
79
- </property>
80
- </properties>
81
- </items>
82
- </parameter>
83
- </parameters>
84
-
85
- <examples>
86
- <example>
87
- <title>Create initial todo list</title>
88
- <command>{"todos": [{"id": "1", "content": "Set up project structure", "status": "pending"}, {"id": "2", "content": "Implement authentication", "status": "pending"}, {"id": "3", "content": "Add tests", "status": "pending"}]}</command>
89
- </example>
90
-
91
- <example>
92
- <title>Update task status</title>
93
- <command>{"todos": [{"id": "1", "content": "Set up project structure", "status": "completed"}, {"id": "2", "content": "Implement authentication", "status": "in_progress"}, {"id": "3", "content": "Add tests", "status": "pending"}]}</command>
94
- </example>
95
- </examples>
96
- </tool_prompt>
tunacode/tools/todo.py DELETED
@@ -1,456 +0,0 @@
1
- """Todo management tool for agent integration.
2
-
3
- This tool allows the AI agent to manage todo items during task execution.
4
- It provides functionality for creating, updating, and tracking tasks.
5
- """
6
-
7
- import logging
8
- import uuid
9
- from datetime import datetime
10
- from pathlib import Path
11
- from typing import Any, Dict, List, Literal, Optional, Union
12
-
13
- import defusedxml.ElementTree as ET
14
- from pydantic_ai.exceptions import ModelRetry
15
-
16
- from tunacode.constants import (
17
- MAX_TODO_CONTENT_LENGTH,
18
- MAX_TODOS_PER_SESSION,
19
- TODO_PRIORITIES,
20
- TodoPriority,
21
- TodoStatus,
22
- )
23
- from tunacode.types import TodoItem, ToolResult, UILogger
24
-
25
- from .base import BaseTool
26
-
27
- logger = logging.getLogger(__name__)
28
-
29
-
30
- class TodoTool(BaseTool):
31
- """Tool for managing todo items from the AI agent."""
32
-
33
- def _get_base_prompt(self) -> str:
34
- """Load and return the base prompt from XML file.
35
-
36
- Returns:
37
- str: The loaded prompt from XML or a default prompt
38
- """
39
- try:
40
- # Load prompt from XML file
41
- prompt_file = Path(__file__).parent / "prompts" / "todo_prompt.xml"
42
- if prompt_file.exists():
43
- tree = ET.parse(prompt_file)
44
- root = tree.getroot()
45
- description = root.find("description")
46
- if description is not None:
47
- return description.text.strip()
48
- except Exception as e:
49
- logger.warning(f"Failed to load XML prompt for todo: {e}")
50
-
51
- # Fallback to default prompt
52
- return """Use this tool to create and manage a structured task list"""
53
-
54
- def _get_parameters_schema(self) -> Dict[str, Any]:
55
- """Get the parameters schema for todo tool.
56
-
57
- Returns:
58
- Dict containing the JSON schema for tool parameters
59
- """
60
- # Try to load from XML first
61
- try:
62
- prompt_file = Path(__file__).parent / "prompts" / "todo_prompt.xml"
63
- if prompt_file.exists():
64
- tree = ET.parse(prompt_file)
65
- root = tree.getroot()
66
- parameters = root.find("parameters")
67
- if parameters is not None:
68
- schema: Dict[str, Any] = {"type": "object", "properties": {}, "required": []}
69
- required_fields: List[str] = []
70
-
71
- for param in parameters.findall("parameter"):
72
- name = param.get("name")
73
- required = param.get("required", "false").lower() == "true"
74
- param_type = param.find("type")
75
- description = param.find("description")
76
-
77
- if name and param_type is not None:
78
- prop = {
79
- "type": param_type.text.strip(),
80
- "description": description.text.strip()
81
- if description is not None
82
- else "",
83
- }
84
-
85
- # Handle array types and nested objects
86
- if param_type.text.strip() == "array":
87
- items = param.find("items")
88
- if items is not None:
89
- item_type = items.find("type")
90
- if item_type is not None and item_type.text.strip() == "object":
91
- # Handle object items
92
- item_props = {}
93
- item_properties = items.find("properties")
94
- if item_properties is not None:
95
- for item_prop in item_properties.findall("property"):
96
- prop_name = item_prop.get("name")
97
- prop_type_elem = item_prop.find("type")
98
- if prop_name and prop_type_elem is not None:
99
- item_props[prop_name] = {
100
- "type": prop_type_elem.text.strip()
101
- }
102
- prop["items"] = {"type": "object", "properties": item_props}
103
- else:
104
- prop["items"] = {"type": items.text.strip()}
105
-
106
- schema["properties"][name] = prop
107
- if required:
108
- required_fields.append(name)
109
-
110
- schema["required"] = required_fields
111
- return schema
112
- except Exception as e:
113
- logger.warning(f"Failed to load parameters from XML for todo: {e}")
114
-
115
- # Fallback to hardcoded schema
116
- return {
117
- "type": "object",
118
- "properties": {
119
- "todos": {
120
- "type": "array",
121
- "description": "The updated todo list",
122
- "items": {
123
- "type": "object",
124
- "properties": {
125
- "id": {"type": "string"},
126
- "content": {"type": "string"},
127
- "status": {"type": "string"},
128
- },
129
- },
130
- },
131
- },
132
- "required": ["todos"],
133
- }
134
-
135
- def __init__(self, state_manager, ui_logger: UILogger | None = None):
136
- """Initialize the todo tool.
137
-
138
- Args:
139
- state_manager: StateManager instance for accessing todos
140
- ui_logger: UI logger instance for displaying messages
141
- """
142
- super().__init__(ui_logger)
143
- self.state_manager = state_manager
144
-
145
- @property
146
- def tool_name(self) -> str:
147
- return "todo"
148
-
149
- async def _execute(
150
- self,
151
- action: Literal["add", "add_multiple", "update", "complete", "list", "remove"],
152
- content: Optional[Union[str, List[str]]] = None,
153
- todo_id: Optional[str] = None,
154
- status: Optional[Literal["pending", "in_progress", "completed"]] = None,
155
- priority: Optional[Literal["high", "medium", "low"]] = None,
156
- todos: Optional[List[dict]] = None,
157
- ) -> ToolResult:
158
- """Execute todo management actions.
159
-
160
- Args:
161
- action: The action to perform (add, add_multiple, update, complete, list, remove)
162
- content: Content for new todos or updates (can be string or list for add_multiple)
163
- todo_id: ID of existing todo for updates/completion
164
- status: Status to set for updates
165
- priority: Priority to set for new/updated todos
166
- todos: List of todo dictionaries for add_multiple action (format: [{"content": "...", "priority": "..."}])
167
-
168
- Returns:
169
- str: Result message describing what was done
170
-
171
- Raises:
172
- ModelRetry: When invalid parameters are provided
173
- """
174
- if action == "add":
175
- if isinstance(content, list):
176
- raise ModelRetry("Use 'add_multiple' action for adding multiple todos")
177
- return await self._add_todo(content, priority)
178
- elif action == "add_multiple":
179
- return await self._add_multiple_todos(content, todos, priority)
180
- elif action == "update":
181
- if isinstance(content, list):
182
- raise ModelRetry("Cannot update with list content")
183
- return await self._update_todo(todo_id, status, priority, content)
184
- elif action == "complete":
185
- return await self._complete_todo(todo_id)
186
- elif action == "list":
187
- return await self._list_todos()
188
- elif action == "remove":
189
- return await self._remove_todo(todo_id)
190
- else:
191
- raise ModelRetry(
192
- f"Invalid action '{action}'. Must be one of: add, add_multiple, update, complete, list, remove"
193
- )
194
-
195
- async def _add_todo(self, content: Optional[str], priority: Optional[str]) -> ToolResult:
196
- """Add a new todo item."""
197
- if not content:
198
- raise ModelRetry("Content is required when adding a todo")
199
-
200
- # Validate content length
201
- if len(content) > MAX_TODO_CONTENT_LENGTH:
202
- raise ModelRetry(
203
- f"Todo content is too long. Maximum length is {MAX_TODO_CONTENT_LENGTH} characters"
204
- )
205
-
206
- # Check todo limit
207
- if len(self.state_manager.session.todos) >= MAX_TODOS_PER_SESSION:
208
- raise ModelRetry(
209
- f"Cannot add more todos. Maximum of {MAX_TODOS_PER_SESSION} todos allowed per session"
210
- )
211
-
212
- # Generate UUID for guaranteed uniqueness
213
- new_id = f"todo_{uuid.uuid4().hex[:8]}"
214
-
215
- # Default priority if not specified
216
- todo_priority = priority or TodoPriority.MEDIUM
217
- if todo_priority not in [p.value for p in TodoPriority]:
218
- raise ModelRetry(
219
- f"Invalid priority '{todo_priority}'. Must be one of: {', '.join([p.value for p in TodoPriority])}"
220
- )
221
-
222
- new_todo = TodoItem(
223
- id=new_id,
224
- content=content,
225
- status=TodoStatus.PENDING,
226
- priority=todo_priority,
227
- created_at=datetime.now(),
228
- )
229
-
230
- self.state_manager.add_todo(new_todo)
231
- return f"Added todo {new_id}: {content} (priority: {todo_priority})"
232
-
233
- async def _add_multiple_todos(
234
- self,
235
- content: Optional[Union[str, List[str]]],
236
- todos: Optional[List[dict]],
237
- priority: Optional[str],
238
- ) -> ToolResult:
239
- """Add multiple todo items at once."""
240
-
241
- # Handle different input formats
242
- todos_to_add = []
243
-
244
- if todos:
245
- # Structured format: [{"content": "...", "priority": "..."}, ...]
246
- for todo_data in todos:
247
- if not isinstance(todo_data, dict) or "content" not in todo_data:
248
- raise ModelRetry("Each todo must be a dict with 'content' field")
249
- todo_content = todo_data["content"]
250
- todo_priority = todo_data.get("priority", priority or TodoPriority.MEDIUM)
251
- if todo_priority not in TODO_PRIORITIES:
252
- raise ModelRetry(
253
- f"Invalid priority '{todo_priority}'. Must be one of: {', '.join(TODO_PRIORITIES)}"
254
- )
255
- todos_to_add.append((todo_content, todo_priority))
256
- elif isinstance(content, list):
257
- # List of strings format: ["task1", "task2", ...]
258
- default_priority = priority or TodoPriority.MEDIUM
259
- if default_priority not in TODO_PRIORITIES:
260
- raise ModelRetry(
261
- f"Invalid priority '{default_priority}'. Must be one of: {', '.join(TODO_PRIORITIES)}"
262
- )
263
- for task_content in content:
264
- if not isinstance(task_content, str):
265
- raise ModelRetry("All content items must be strings")
266
- todos_to_add.append((task_content, default_priority))
267
- else:
268
- raise ModelRetry(
269
- "For add_multiple, provide either 'todos' list or 'content' as list of strings"
270
- )
271
-
272
- if not todos_to_add:
273
- raise ModelRetry("No todos to add")
274
-
275
- # Check todo limit
276
- current_count = len(self.state_manager.session.todos)
277
- if current_count + len(todos_to_add) > MAX_TODOS_PER_SESSION:
278
- available = MAX_TODOS_PER_SESSION - current_count
279
- raise ModelRetry(
280
- f"Cannot add {len(todos_to_add)} todos. Only {available} slots available (max {MAX_TODOS_PER_SESSION} per session)"
281
- )
282
-
283
- # Add all todos
284
- added_ids = []
285
- for task_content, task_priority in todos_to_add:
286
- # Validate content length
287
- if len(task_content) > MAX_TODO_CONTENT_LENGTH:
288
- raise ModelRetry(
289
- f"Todo content is too long: '{task_content[:50]}...'. Maximum length is {MAX_TODO_CONTENT_LENGTH} characters"
290
- )
291
-
292
- # Generate UUID for guaranteed uniqueness
293
- new_id = f"todo_{uuid.uuid4().hex[:8]}"
294
-
295
- new_todo = TodoItem(
296
- id=new_id,
297
- content=task_content,
298
- status=TodoStatus.PENDING,
299
- priority=task_priority,
300
- created_at=datetime.now(),
301
- )
302
-
303
- self.state_manager.add_todo(new_todo)
304
- added_ids.append(new_id)
305
-
306
- count = len(added_ids)
307
- return f"Added {count} todos (IDs: {', '.join(added_ids)})"
308
-
309
- async def _update_todo(
310
- self,
311
- todo_id: Optional[str],
312
- status: Optional[str],
313
- priority: Optional[str],
314
- content: Optional[str],
315
- ) -> ToolResult:
316
- """Update an existing todo item."""
317
- if not todo_id:
318
- raise ModelRetry("Todo ID is required for updates")
319
-
320
- # Find the todo
321
- todo = None
322
- for t in self.state_manager.session.todos:
323
- if t.id == todo_id:
324
- todo = t
325
- break
326
-
327
- if not todo:
328
- raise ModelRetry(f"Todo with ID '{todo_id}' not found")
329
-
330
- changes = []
331
-
332
- # Update status if provided
333
- if status:
334
- if status not in [s.value for s in TodoStatus]:
335
- raise ModelRetry(
336
- f"Invalid status '{status}'. Must be one of: {', '.join([s.value for s in TodoStatus])}"
337
- )
338
- todo.status = status
339
- if status == TodoStatus.COMPLETED.value and not todo.completed_at:
340
- todo.completed_at = datetime.now()
341
- changes.append(f"status to {status}")
342
-
343
- # Update priority if provided
344
- if priority:
345
- if priority not in [p.value for p in TodoPriority]:
346
- raise ModelRetry(
347
- f"Invalid priority '{priority}'. Must be one of: {', '.join([p.value for p in TodoPriority])}"
348
- )
349
- todo.priority = priority
350
- changes.append(f"priority to {priority}")
351
-
352
- # Update content if provided
353
- if content:
354
- todo.content = content
355
- changes.append(f"content to '{content}'")
356
-
357
- if not changes:
358
- raise ModelRetry(
359
- "At least one of status, priority, or content must be provided for updates"
360
- )
361
-
362
- change_summary = ", ".join(changes)
363
- return f"Updated todo {todo_id}: {change_summary}"
364
-
365
- async def _complete_todo(self, todo_id: Optional[str]) -> ToolResult:
366
- """Mark a todo as completed."""
367
- if not todo_id:
368
- raise ModelRetry("Todo ID is required to mark as complete")
369
-
370
- # Find and update the todo
371
- for todo in self.state_manager.session.todos:
372
- if todo.id == todo_id:
373
- todo.status = TodoStatus.COMPLETED.value
374
- todo.completed_at = datetime.now()
375
- return f"Marked todo {todo_id} as completed: {todo.content}"
376
-
377
- raise ModelRetry(f"Todo with ID '{todo_id}' not found")
378
-
379
- async def _list_todos(self) -> ToolResult:
380
- """List all current todos."""
381
- todos = self.state_manager.session.todos
382
- if not todos:
383
- return "No todos found"
384
-
385
- # Group by status for better organization
386
- pending = [t for t in todos if t.status == TodoStatus.PENDING.value]
387
- in_progress = [t for t in todos if t.status == TodoStatus.IN_PROGRESS.value]
388
- completed = [t for t in todos if t.status == TodoStatus.COMPLETED.value]
389
-
390
- lines = []
391
-
392
- if in_progress:
393
- lines.append("IN PROGRESS:")
394
- for todo in in_progress:
395
- lines.append(f" {todo.id}: {todo.content} (priority: {todo.priority})")
396
-
397
- if pending:
398
- lines.append("\nPENDING:")
399
- for todo in pending:
400
- lines.append(f" {todo.id}: {todo.content} (priority: {todo.priority})")
401
-
402
- if completed:
403
- lines.append("\nCOMPLETED:")
404
- for todo in completed:
405
- lines.append(f" {todo.id}: {todo.content}")
406
-
407
- return "\n".join(lines)
408
-
409
- async def _remove_todo(self, todo_id: Optional[str]) -> ToolResult:
410
- """Remove a todo item."""
411
- if not todo_id:
412
- raise ModelRetry("Todo ID is required to remove a todo")
413
-
414
- # Find the todo first to get its content for the response
415
- todo_content = None
416
- for todo in self.state_manager.session.todos:
417
- if todo.id == todo_id:
418
- todo_content = todo.content
419
- break
420
-
421
- if not todo_content:
422
- raise ModelRetry(f"Todo with ID '{todo_id}' not found")
423
-
424
- self.state_manager.remove_todo(todo_id)
425
- return f"Removed todo {todo_id}: {todo_content}"
426
-
427
- def get_current_todos_sync(self) -> str:
428
- """Get current todos synchronously for system prompt inclusion."""
429
- todos = self.state_manager.session.todos
430
-
431
- if not todos:
432
- return "No todos found"
433
-
434
- # Group by status for better organization
435
- pending = [t for t in todos if t.status == TodoStatus.PENDING.value]
436
- in_progress = [t for t in todos if t.status == TodoStatus.IN_PROGRESS.value]
437
- completed = [t for t in todos if t.status == TodoStatus.COMPLETED.value]
438
-
439
- lines = []
440
-
441
- if in_progress:
442
- lines.append("IN PROGRESS:")
443
- for todo in in_progress:
444
- lines.append(f" {todo.id}: {todo.content} (priority: {todo.priority})")
445
-
446
- if pending:
447
- lines.append("\nPENDING:")
448
- for todo in pending:
449
- lines.append(f" {todo.id}: {todo.content} (priority: {todo.priority})")
450
-
451
- if completed:
452
- lines.append("\nCOMPLETED:")
453
- for todo in completed:
454
- lines.append(f" {todo.id}: {todo.content}")
455
-
456
- return "\n".join(lines)