todo-agent 0.3.2__py3-none-any.whl → 0.3.3__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.
- todo_agent/_version.py +2 -2
- todo_agent/core/exceptions.py +6 -6
- todo_agent/core/todo_manager.py +13 -8
- todo_agent/infrastructure/inference.py +113 -52
- todo_agent/infrastructure/llm_client.py +56 -22
- todo_agent/infrastructure/ollama_client.py +23 -13
- todo_agent/infrastructure/openrouter_client.py +20 -12
- todo_agent/infrastructure/prompts/system_prompt.txt +88 -438
- todo_agent/infrastructure/todo_shell.py +35 -11
- todo_agent/interface/cli.py +51 -33
- todo_agent/interface/formatters.py +7 -4
- todo_agent/interface/progress.py +30 -19
- todo_agent/interface/tools.py +25 -25
- {todo_agent-0.3.2.dist-info → todo_agent-0.3.3.dist-info}/METADATA +1 -1
- todo_agent-0.3.3.dist-info/RECORD +30 -0
- todo_agent-0.3.2.dist-info/RECORD +0 -30
- {todo_agent-0.3.2.dist-info → todo_agent-0.3.3.dist-info}/WHEEL +0 -0
- {todo_agent-0.3.2.dist-info → todo_agent-0.3.3.dist-info}/entry_points.txt +0 -0
- {todo_agent-0.3.2.dist-info → todo_agent-0.3.3.dist-info}/licenses/LICENSE +0 -0
- {todo_agent-0.3.2.dist-info → todo_agent-0.3.3.dist-info}/top_level.txt +0 -0
@@ -32,13 +32,19 @@ class TodoShell:
|
|
32
32
|
self.todo_dir = os.path.dirname(todo_file_path) or os.getcwd()
|
33
33
|
self.logger = logger
|
34
34
|
|
35
|
-
def execute(
|
35
|
+
def execute(
|
36
|
+
self,
|
37
|
+
command: List[str],
|
38
|
+
cwd: Optional[str] = None,
|
39
|
+
suppress_color: bool = False,
|
40
|
+
) -> str:
|
36
41
|
"""
|
37
|
-
Execute todo.sh command.
|
42
|
+
Execute a todo.sh command and return the output.
|
38
43
|
|
39
44
|
Args:
|
40
45
|
command: List of command arguments
|
41
46
|
cwd: Working directory (defaults to todo.sh directory)
|
47
|
+
suppress_color: If True, strip ANSI color codes from output (for LLM consumption)
|
42
48
|
|
43
49
|
Returns:
|
44
50
|
Command output as string
|
@@ -52,6 +58,7 @@ class TodoShell:
|
|
52
58
|
self.logger.debug(f"=== RAW COMMAND EXECUTION ===")
|
53
59
|
self.logger.debug(f"Raw command: {raw_command}")
|
54
60
|
self.logger.debug(f"Working directory: {cwd or self.todo_dir}")
|
61
|
+
self.logger.debug(f"Suppress color: {suppress_color}")
|
55
62
|
|
56
63
|
try:
|
57
64
|
working_dir = cwd or self.todo_dir
|
@@ -67,7 +74,20 @@ class TodoShell:
|
|
67
74
|
self.logger.debug(f"Raw stderr: {result.stderr}")
|
68
75
|
self.logger.debug(f"Return code: {result.returncode}")
|
69
76
|
|
70
|
-
|
77
|
+
output = result.stdout.strip()
|
78
|
+
|
79
|
+
# Strip ANSI color codes if requested (for LLM consumption)
|
80
|
+
if suppress_color:
|
81
|
+
from rich.text import Text
|
82
|
+
|
83
|
+
# Use Rich's Text.from_ansi to parse and then get plain text
|
84
|
+
output = Text.from_ansi(output).plain
|
85
|
+
if self.logger:
|
86
|
+
self.logger.debug(
|
87
|
+
f"Stripped ANSI codes from output for LLM consumption"
|
88
|
+
)
|
89
|
+
|
90
|
+
return output
|
71
91
|
except subprocess.CalledProcessError as e:
|
72
92
|
# Log error details
|
73
93
|
if self.logger:
|
@@ -88,12 +108,14 @@ class TodoShell:
|
|
88
108
|
"""Add new task."""
|
89
109
|
return self.execute(["todo.sh", "add", description])
|
90
110
|
|
91
|
-
def list_tasks(
|
111
|
+
def list_tasks(
|
112
|
+
self, filter_str: Optional[str] = None, suppress_color: bool = True
|
113
|
+
) -> str:
|
92
114
|
"""List tasks with optional filtering."""
|
93
115
|
command = ["todo.sh", "ls"]
|
94
116
|
if filter_str:
|
95
117
|
command.append(filter_str)
|
96
|
-
return self.execute(command)
|
118
|
+
return self.execute(command, suppress_color=suppress_color)
|
97
119
|
|
98
120
|
def complete(self, task_number: int) -> str:
|
99
121
|
"""Mark task complete."""
|
@@ -135,20 +157,22 @@ class TodoShell:
|
|
135
157
|
"""Remove task priority."""
|
136
158
|
return self.execute(["todo.sh", "depri", str(task_number)])
|
137
159
|
|
138
|
-
def list_projects(self) -> str:
|
160
|
+
def list_projects(self, suppress_color: bool = True) -> str:
|
139
161
|
"""List projects."""
|
140
|
-
return self.execute(["todo.sh", "lsp"])
|
162
|
+
return self.execute(["todo.sh", "lsp"], suppress_color=suppress_color)
|
141
163
|
|
142
|
-
def list_contexts(self) -> str:
|
164
|
+
def list_contexts(self, suppress_color: bool = True) -> str:
|
143
165
|
"""List contexts."""
|
144
|
-
return self.execute(["todo.sh", "lsc"])
|
166
|
+
return self.execute(["todo.sh", "lsc"], suppress_color=suppress_color)
|
145
167
|
|
146
|
-
def list_completed(
|
168
|
+
def list_completed(
|
169
|
+
self, filter_str: Optional[str] = None, suppress_color: bool = True
|
170
|
+
) -> str:
|
147
171
|
"""List completed tasks with optional filtering."""
|
148
172
|
command = ["todo.sh", "listfile", "done.txt"]
|
149
173
|
if filter_str:
|
150
174
|
command.append(filter_str)
|
151
|
-
return self.execute(command)
|
175
|
+
return self.execute(command, suppress_color=suppress_color)
|
152
176
|
|
153
177
|
def archive(self) -> str:
|
154
178
|
"""Archive completed tasks."""
|
todo_agent/interface/cli.py
CHANGED
@@ -2,8 +2,9 @@
|
|
2
2
|
Command-line interface for todo.sh LLM agent.
|
3
3
|
"""
|
4
4
|
|
5
|
-
from typing import Optional
|
6
5
|
import readline
|
6
|
+
from typing import Optional
|
7
|
+
|
7
8
|
from rich.align import Align
|
8
9
|
from rich.console import Console, Group
|
9
10
|
from rich.live import Live
|
@@ -23,8 +24,8 @@ try:
|
|
23
24
|
TableFormatter,
|
24
25
|
TaskFormatter,
|
25
26
|
)
|
26
|
-
from todo_agent.interface.tools import ToolCallHandler
|
27
27
|
from todo_agent.interface.progress import ToolCallProgress
|
28
|
+
from todo_agent.interface.tools import ToolCallHandler
|
28
29
|
except ImportError:
|
29
30
|
from core.todo_manager import TodoManager # type: ignore[no-redef]
|
30
31
|
from infrastructure.config import Config # type: ignore[no-redef]
|
@@ -38,8 +39,8 @@ except ImportError:
|
|
38
39
|
TableFormatter,
|
39
40
|
TaskFormatter,
|
40
41
|
)
|
41
|
-
from interface.tools import ToolCallHandler # type: ignore[no-redef]
|
42
42
|
from interface.progress import ToolCallProgress # type: ignore[no-redef]
|
43
|
+
from interface.tools import ToolCallHandler # type: ignore[no-redef]
|
43
44
|
|
44
45
|
|
45
46
|
class CLI:
|
@@ -100,22 +101,23 @@ class CLI:
|
|
100
101
|
initial_spinner = self._create_thinking_spinner("Thinking...")
|
101
102
|
return Live(initial_spinner, console=self.console, refresh_per_second=10)
|
102
103
|
|
103
|
-
def _create_tool_call_spinner(
|
104
|
-
|
104
|
+
def _create_tool_call_spinner(
|
105
|
+
self, progress_description: str, sequence: int = 0, total_sequences: int = 0
|
106
|
+
) -> Group:
|
105
107
|
"""
|
106
108
|
Create a multi-line spinner showing tool call progress.
|
107
|
-
|
109
|
+
|
108
110
|
Args:
|
109
111
|
progress_description: User-friendly description of what the tool is doing
|
110
112
|
sequence: Current sequence number
|
111
113
|
total_sequences: Total number of sequences
|
112
|
-
|
114
|
+
|
113
115
|
Returns:
|
114
116
|
Group object with spinner and optional sequence info
|
115
117
|
"""
|
116
118
|
# Line 1: Main progress with spinner
|
117
119
|
main_line = Spinner("dots", text=Text(progress_description, style="cyan"))
|
118
|
-
|
120
|
+
|
119
121
|
# Line 2: Sequence progress (show current sequence even if we don't know total)
|
120
122
|
# if sequence > 0:
|
121
123
|
# if total_sequences > 0:
|
@@ -123,75 +125,87 @@ class CLI:
|
|
123
125
|
# else:
|
124
126
|
# sequence_text = Text(f"Sequence {sequence}", style="dim")
|
125
127
|
# return Group(main_line, sequence_text)
|
126
|
-
|
127
|
-
return main_line
|
128
|
+
|
129
|
+
return Group(main_line)
|
128
130
|
|
129
131
|
def _create_completion_spinner(self, thinking_time: float) -> Spinner:
|
130
132
|
"""
|
131
133
|
Create completion spinner with timing.
|
132
|
-
|
134
|
+
|
133
135
|
Args:
|
134
136
|
thinking_time: Total thinking time in seconds
|
135
|
-
|
137
|
+
|
136
138
|
Returns:
|
137
139
|
Spinner object showing completion
|
138
140
|
"""
|
139
|
-
return Spinner(
|
141
|
+
return Spinner(
|
142
|
+
"dots", text=Text(f"✅ Complete ({thinking_time:.1f}s)", style="green")
|
143
|
+
)
|
140
144
|
|
141
145
|
def _create_cli_progress_callback(self, live_display: Live) -> ToolCallProgress:
|
142
146
|
"""
|
143
147
|
Create a CLI-specific progress callback for tool call tracking.
|
144
|
-
|
148
|
+
|
145
149
|
Args:
|
146
150
|
live_display: The live display to update
|
147
|
-
|
151
|
+
|
148
152
|
Returns:
|
149
153
|
ToolCallProgress implementation for CLI
|
150
154
|
"""
|
155
|
+
|
151
156
|
class CLIProgressCallback(ToolCallProgress):
|
152
157
|
def __init__(self, cli: CLI, live: Live):
|
153
158
|
self.cli = cli
|
154
159
|
self.live = live
|
155
160
|
self.current_sequence = 0
|
156
161
|
self.total_sequences = 0
|
157
|
-
|
162
|
+
|
158
163
|
def on_thinking_start(self) -> None:
|
159
164
|
"""Show initial thinking spinner."""
|
160
|
-
spinner = self.cli._create_thinking_spinner(
|
165
|
+
spinner = self.cli._create_thinking_spinner(
|
166
|
+
"🤔 Analyzing your request..."
|
167
|
+
)
|
161
168
|
self.live.update(spinner)
|
162
|
-
|
163
|
-
def on_tool_call_start(
|
164
|
-
|
169
|
+
|
170
|
+
def on_tool_call_start(
|
171
|
+
self,
|
172
|
+
tool_name: str,
|
173
|
+
progress_description: str,
|
174
|
+
sequence: int,
|
175
|
+
total_sequences: int,
|
176
|
+
) -> None:
|
165
177
|
"""Show tool execution progress."""
|
166
178
|
self.current_sequence = sequence
|
167
179
|
self.total_sequences = total_sequences
|
168
|
-
|
180
|
+
|
169
181
|
# Create multi-line spinner
|
170
182
|
spinner = self.cli._create_tool_call_spinner(
|
171
183
|
progress_description=progress_description,
|
172
184
|
sequence=sequence,
|
173
|
-
total_sequences=total_sequences
|
185
|
+
total_sequences=total_sequences,
|
174
186
|
)
|
175
187
|
self.live.update(spinner)
|
176
|
-
|
177
|
-
def on_tool_call_complete(
|
188
|
+
|
189
|
+
def on_tool_call_complete(
|
190
|
+
self, tool_name: str, success: bool, duration: float
|
191
|
+
) -> None:
|
178
192
|
"""Tool completion - no action needed."""
|
179
193
|
pass
|
180
|
-
|
194
|
+
|
181
195
|
def on_sequence_complete(self, sequence: int, total_sequences: int) -> None:
|
182
196
|
"""Show sequence completion."""
|
183
197
|
spinner = self.cli._create_tool_call_spinner(
|
184
198
|
progress_description=f"🔄 Sequence {sequence} complete",
|
185
199
|
sequence=sequence,
|
186
|
-
total_sequences=total_sequences
|
200
|
+
total_sequences=total_sequences,
|
187
201
|
)
|
188
202
|
self.live.update(spinner)
|
189
|
-
|
203
|
+
|
190
204
|
def on_thinking_complete(self, total_time: float) -> None:
|
191
205
|
"""Show completion spinner."""
|
192
206
|
spinner = self.cli._create_completion_spinner(total_time)
|
193
207
|
self.live.update(spinner)
|
194
|
-
|
208
|
+
|
195
209
|
return CLIProgressCallback(self, live_display)
|
196
210
|
|
197
211
|
def _print_header(self) -> None:
|
@@ -296,7 +310,8 @@ class CLI:
|
|
296
310
|
if user_input.lower() == "list":
|
297
311
|
self.logger.debug("User requested task list")
|
298
312
|
try:
|
299
|
-
|
313
|
+
# Use suppress_color=False for interactive display to preserve colors
|
314
|
+
output = self.todo_shell.list_tasks(suppress_color=False)
|
300
315
|
formatted_output = TaskFormatter.format_task_list(output)
|
301
316
|
task_panel = PanelFormatter.create_task_panel(formatted_output)
|
302
317
|
self.console.print(task_panel)
|
@@ -311,7 +326,8 @@ class CLI:
|
|
311
326
|
if user_input.lower() == "done":
|
312
327
|
self.logger.debug("User requested completed task list")
|
313
328
|
try:
|
314
|
-
|
329
|
+
# Use suppress_color=False for interactive display to preserve colors
|
330
|
+
output = self.todo_shell.list_completed(suppress_color=False)
|
315
331
|
formatted_output = TaskFormatter.format_completed_tasks(output)
|
316
332
|
task_panel = PanelFormatter.create_task_panel(
|
317
333
|
formatted_output, title="✅ Completed Tasks"
|
@@ -336,7 +352,7 @@ class CLI:
|
|
336
352
|
# Get memory usage
|
337
353
|
# DISABLED FOR NOW
|
338
354
|
memory_usage = self._get_memory_usage()
|
339
|
-
#memory_usage = None
|
355
|
+
# memory_usage = None
|
340
356
|
|
341
357
|
# Create response panel with memory usage
|
342
358
|
response_panel = PanelFormatter.create_response_panel(
|
@@ -368,9 +384,11 @@ class CLI:
|
|
368
384
|
try:
|
369
385
|
# Create progress callback for tool call tracking
|
370
386
|
progress_callback = self._create_cli_progress_callback(live)
|
371
|
-
|
387
|
+
|
372
388
|
# Process request through inference engine with progress tracking
|
373
|
-
response, thinking_time = self.inference.process_request(
|
389
|
+
response, thinking_time = self.inference.process_request(
|
390
|
+
user_input, progress_callback
|
391
|
+
)
|
374
392
|
|
375
393
|
# Update spinner with completion message and thinking time
|
376
394
|
live.update(
|
@@ -21,20 +21,23 @@ PROVIDER_ERROR_MESSAGES = {
|
|
21
21
|
"rate_limit": "I'm a bit overwhelmed right now. Please wait a moment and try again, or type 'clear' to start fresh.",
|
22
22
|
"auth_error": "I can't connect to my AI service. Please check your configuration, or type 'clear' to reset.",
|
23
23
|
"timeout": "The request took too long. Please try again, or type 'clear' to reset our conversation.",
|
24
|
-
"general_error": "Something went wrong with my AI service. Please try again, or type 'clear' to reset our conversation."
|
24
|
+
"general_error": "Something went wrong with my AI service. Please try again, or type 'clear' to reset our conversation.",
|
25
25
|
}
|
26
26
|
|
27
|
+
|
27
28
|
def get_provider_error_message(error_type: str) -> str:
|
28
29
|
"""
|
29
30
|
Get user-friendly error message for provider errors.
|
30
|
-
|
31
|
+
|
31
32
|
Args:
|
32
33
|
error_type: The type of provider error
|
33
|
-
|
34
|
+
|
34
35
|
Returns:
|
35
36
|
User-friendly error message with recovery suggestion
|
36
37
|
"""
|
37
|
-
return PROVIDER_ERROR_MESSAGES.get(
|
38
|
+
return PROVIDER_ERROR_MESSAGES.get(
|
39
|
+
error_type, PROVIDER_ERROR_MESSAGES["general_error"]
|
40
|
+
)
|
38
41
|
|
39
42
|
|
40
43
|
class TaskFormatter:
|
todo_agent/interface/progress.py
CHANGED
@@ -3,34 +3,39 @@ Progress tracking interface for tool call execution.
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
from abc import ABC, abstractmethod
|
6
|
-
from typing import Optional
|
7
6
|
|
8
7
|
|
9
8
|
class ToolCallProgress(ABC):
|
10
9
|
"""Abstract interface for tool call progress tracking."""
|
11
|
-
|
10
|
+
|
12
11
|
@abstractmethod
|
13
12
|
def on_thinking_start(self) -> None:
|
14
13
|
"""Called when LLM starts thinking."""
|
15
14
|
pass
|
16
|
-
|
15
|
+
|
17
16
|
@abstractmethod
|
18
|
-
def on_tool_call_start(
|
19
|
-
|
17
|
+
def on_tool_call_start(
|
18
|
+
self,
|
19
|
+
tool_name: str,
|
20
|
+
progress_description: str,
|
21
|
+
sequence: int,
|
22
|
+
total_sequences: int,
|
23
|
+
) -> None:
|
20
24
|
"""Called when a tool call starts."""
|
21
25
|
pass
|
22
|
-
|
26
|
+
|
23
27
|
@abstractmethod
|
24
|
-
def on_tool_call_complete(
|
25
|
-
|
28
|
+
def on_tool_call_complete(
|
29
|
+
self, tool_name: str, success: bool, duration: float
|
30
|
+
) -> None:
|
26
31
|
"""Called when a tool call completes (optional - no action needed)."""
|
27
32
|
pass
|
28
|
-
|
33
|
+
|
29
34
|
@abstractmethod
|
30
35
|
def on_sequence_complete(self, sequence: int, total_sequences: int) -> None:
|
31
36
|
"""Called when a tool call sequence completes."""
|
32
37
|
pass
|
33
|
-
|
38
|
+
|
34
39
|
@abstractmethod
|
35
40
|
def on_thinking_complete(self, total_time: float) -> None:
|
36
41
|
"""Called when thinking is complete."""
|
@@ -39,20 +44,26 @@ class ToolCallProgress(ABC):
|
|
39
44
|
|
40
45
|
class NoOpProgress(ToolCallProgress):
|
41
46
|
"""No-operation implementation for when progress tracking is not needed."""
|
42
|
-
|
47
|
+
|
43
48
|
def on_thinking_start(self) -> None:
|
44
49
|
pass
|
45
|
-
|
46
|
-
def on_tool_call_start(
|
47
|
-
|
50
|
+
|
51
|
+
def on_tool_call_start(
|
52
|
+
self,
|
53
|
+
tool_name: str,
|
54
|
+
progress_description: str,
|
55
|
+
sequence: int,
|
56
|
+
total_sequences: int,
|
57
|
+
) -> None:
|
48
58
|
pass
|
49
|
-
|
50
|
-
def on_tool_call_complete(
|
51
|
-
|
59
|
+
|
60
|
+
def on_tool_call_complete(
|
61
|
+
self, tool_name: str, success: bool, duration: float
|
62
|
+
) -> None:
|
52
63
|
pass
|
53
|
-
|
64
|
+
|
54
65
|
def on_sequence_complete(self, sequence: int, total_sequences: int) -> None:
|
55
66
|
pass
|
56
|
-
|
67
|
+
|
57
68
|
def on_thinking_complete(self, total_time: float) -> None:
|
58
69
|
pass
|
todo_agent/interface/tools.py
CHANGED
@@ -180,7 +180,7 @@ class ToolCallHandler:
|
|
180
180
|
"required": ["description"],
|
181
181
|
},
|
182
182
|
},
|
183
|
-
"progress_description": "
|
183
|
+
"progress_description": "✨ Creating new task: {description}...",
|
184
184
|
},
|
185
185
|
{
|
186
186
|
"type": "function",
|
@@ -204,7 +204,7 @@ class ToolCallHandler:
|
|
204
204
|
"required": ["task_number"],
|
205
205
|
},
|
206
206
|
},
|
207
|
-
"progress_description": "🎯 Marking task complete...",
|
207
|
+
"progress_description": "🎯 Marking task #{task_number} as complete...",
|
208
208
|
},
|
209
209
|
{
|
210
210
|
"type": "function",
|
@@ -232,7 +232,7 @@ class ToolCallHandler:
|
|
232
232
|
"required": ["task_number", "new_description"],
|
233
233
|
},
|
234
234
|
},
|
235
|
-
"progress_description": "✏️ Updating task description...",
|
235
|
+
"progress_description": "✏️ Updating task #{task_number} with new description...",
|
236
236
|
},
|
237
237
|
{
|
238
238
|
"type": "function",
|
@@ -262,7 +262,7 @@ class ToolCallHandler:
|
|
262
262
|
"required": ["task_number", "text_to_append"],
|
263
263
|
},
|
264
264
|
},
|
265
|
-
"progress_description": "📝 Adding notes to task...",
|
265
|
+
"progress_description": "📝 Adding notes to task #{task_number}...",
|
266
266
|
},
|
267
267
|
{
|
268
268
|
"type": "function",
|
@@ -289,7 +289,7 @@ class ToolCallHandler:
|
|
289
289
|
"required": ["task_number", "text"],
|
290
290
|
},
|
291
291
|
},
|
292
|
-
"progress_description": "📝 Adding prefix to task...",
|
292
|
+
"progress_description": "📝 Adding prefix to task #{task_number}...",
|
293
293
|
},
|
294
294
|
{
|
295
295
|
"type": "function",
|
@@ -320,7 +320,7 @@ class ToolCallHandler:
|
|
320
320
|
"required": ["task_number"],
|
321
321
|
},
|
322
322
|
},
|
323
|
-
"progress_description": "🗑️ Deleting task...",
|
323
|
+
"progress_description": "🗑️ Deleting task #{task_number}...",
|
324
324
|
},
|
325
325
|
{
|
326
326
|
"type": "function",
|
@@ -348,7 +348,7 @@ class ToolCallHandler:
|
|
348
348
|
"required": ["task_number", "priority"],
|
349
349
|
},
|
350
350
|
},
|
351
|
-
"progress_description": "🏷️ Setting priority...",
|
351
|
+
"progress_description": "🏷️ Setting priority {priority} for task #{task_number}...",
|
352
352
|
},
|
353
353
|
{
|
354
354
|
"type": "function",
|
@@ -372,7 +372,7 @@ class ToolCallHandler:
|
|
372
372
|
"required": ["task_number"],
|
373
373
|
},
|
374
374
|
},
|
375
|
-
"progress_description": "🏷️ Removing priority...",
|
375
|
+
"progress_description": "🏷️ Removing priority from task #{task_number}...",
|
376
376
|
},
|
377
377
|
{
|
378
378
|
"type": "function",
|
@@ -404,7 +404,7 @@ class ToolCallHandler:
|
|
404
404
|
"required": ["task_number", "due_date"],
|
405
405
|
},
|
406
406
|
},
|
407
|
-
"progress_description": "📅 Setting due date...",
|
407
|
+
"progress_description": "📅 Setting due date {due_date} for task #{task_number}...",
|
408
408
|
},
|
409
409
|
{
|
410
410
|
"type": "function",
|
@@ -436,7 +436,7 @@ class ToolCallHandler:
|
|
436
436
|
"required": ["task_number", "context"],
|
437
437
|
},
|
438
438
|
},
|
439
|
-
"progress_description": "📍 Setting context...",
|
439
|
+
"progress_description": "📍 Setting context {context} for task #{task_number}...",
|
440
440
|
},
|
441
441
|
{
|
442
442
|
"type": "function",
|
@@ -472,7 +472,7 @@ class ToolCallHandler:
|
|
472
472
|
"required": ["task_number", "projects"],
|
473
473
|
},
|
474
474
|
},
|
475
|
-
"progress_description": "🏷️ Setting project tags...",
|
475
|
+
"progress_description": "🏷️ Setting project tags for task #{task_number}...",
|
476
476
|
},
|
477
477
|
{
|
478
478
|
"type": "function",
|
@@ -504,7 +504,7 @@ class ToolCallHandler:
|
|
504
504
|
"required": ["task_number", "destination"],
|
505
505
|
},
|
506
506
|
},
|
507
|
-
"progress_description": "📦 Moving task...",
|
507
|
+
"progress_description": "📦 Moving task #{task_number} to {destination}...",
|
508
508
|
},
|
509
509
|
{
|
510
510
|
"type": "function",
|
@@ -544,7 +544,7 @@ class ToolCallHandler:
|
|
544
544
|
"required": ["date_expression"],
|
545
545
|
},
|
546
546
|
},
|
547
|
-
"progress_description": "📅 Converting date expression...",
|
547
|
+
"progress_description": "📅 Converting date expression '{date_expression}'...",
|
548
548
|
},
|
549
549
|
{
|
550
550
|
"type": "function",
|
@@ -580,7 +580,7 @@ class ToolCallHandler:
|
|
580
580
|
"required": ["month", "year"],
|
581
581
|
},
|
582
582
|
},
|
583
|
-
"progress_description": "📅 Generating calendar...",
|
583
|
+
"progress_description": "📅 Generating calendar for {month}/{year}...",
|
584
584
|
},
|
585
585
|
{
|
586
586
|
"type": "function",
|
@@ -619,7 +619,7 @@ class ToolCallHandler:
|
|
619
619
|
"required": ["description"],
|
620
620
|
},
|
621
621
|
},
|
622
|
-
"progress_description": "✅ Creating completed task...",
|
622
|
+
"progress_description": "✅ Creating completed task: {description}...",
|
623
623
|
},
|
624
624
|
{
|
625
625
|
"type": "function",
|
@@ -646,7 +646,7 @@ class ToolCallHandler:
|
|
646
646
|
"required": ["task_number"],
|
647
647
|
},
|
648
648
|
},
|
649
|
-
"progress_description": "🔄 Restoring completed task...",
|
649
|
+
"progress_description": "🔄 Restoring completed task #{task_number}...",
|
650
650
|
},
|
651
651
|
]
|
652
652
|
|
@@ -805,16 +805,16 @@ class ToolCallHandler:
|
|
805
805
|
"""Execute a tool call and return the result."""
|
806
806
|
# Validate tool call structure
|
807
807
|
if not isinstance(tool_call, dict):
|
808
|
-
return {
|
808
|
+
return { # type: ignore[unreachable]
|
809
809
|
"tool_call_id": "unknown",
|
810
810
|
"name": "unknown",
|
811
811
|
"output": "ERROR: Invalid tool call format",
|
812
812
|
"error": True,
|
813
813
|
"error_type": "malformed_tool_call",
|
814
814
|
"error_details": "Tool call is not a dictionary",
|
815
|
-
"user_message": "I received a malformed request. Please try again, or type 'clear' to reset our conversation."
|
815
|
+
"user_message": "I received a malformed request. Please try again, or type 'clear' to reset our conversation.",
|
816
816
|
}
|
817
|
-
|
817
|
+
|
818
818
|
if "function" not in tool_call:
|
819
819
|
return {
|
820
820
|
"tool_call_id": tool_call.get("id", "unknown"),
|
@@ -823,9 +823,9 @@ class ToolCallHandler:
|
|
823
823
|
"error": True,
|
824
824
|
"error_type": "malformed_tool_call",
|
825
825
|
"error_details": "Tool call missing function field",
|
826
|
-
"user_message": "I received a malformed request. Please try again, or type 'clear' to reset our conversation."
|
826
|
+
"user_message": "I received a malformed request. Please try again, or type 'clear' to reset our conversation.",
|
827
827
|
}
|
828
|
-
|
828
|
+
|
829
829
|
function = tool_call["function"]
|
830
830
|
if not isinstance(function, dict):
|
831
831
|
return {
|
@@ -835,9 +835,9 @@ class ToolCallHandler:
|
|
835
835
|
"error": True,
|
836
836
|
"error_type": "malformed_tool_call",
|
837
837
|
"error_details": "Function field is not a dictionary",
|
838
|
-
"user_message": "I received a malformed request. Please try again, or type 'clear' to reset our conversation."
|
838
|
+
"user_message": "I received a malformed request. Please try again, or type 'clear' to reset our conversation.",
|
839
839
|
}
|
840
|
-
|
840
|
+
|
841
841
|
tool_name = function.get("name")
|
842
842
|
if not tool_name:
|
843
843
|
return {
|
@@ -847,9 +847,9 @@ class ToolCallHandler:
|
|
847
847
|
"error": True,
|
848
848
|
"error_type": "malformed_tool_call",
|
849
849
|
"error_details": "Function missing name field",
|
850
|
-
"user_message": "I received a malformed request. Please try again, or type 'clear' to reset our conversation."
|
850
|
+
"user_message": "I received a malformed request. Please try again, or type 'clear' to reset our conversation.",
|
851
851
|
}
|
852
|
-
|
852
|
+
|
853
853
|
arguments = function.get("arguments", {})
|
854
854
|
tool_call_id = tool_call.get("id", "unknown")
|
855
855
|
|
@@ -0,0 +1,30 @@
|
|
1
|
+
todo_agent/__init__.py,sha256=RUowhd14r3tqB_7rl83unGV8oBjra3UOIl7jix-33fk,254
|
2
|
+
todo_agent/_version.py,sha256=lemL_4Kl75FgrO6lVuFrrtw6-Dcf9wtXBalKkXuzkO4,704
|
3
|
+
todo_agent/main.py,sha256=-ryhMm4c4sz4e4anXI8B-CYnpEh5HIkmnYcnGxcWHDk,1628
|
4
|
+
todo_agent/core/__init__.py,sha256=QAZ4it63pXv5-DxtNcuSAmg7ZnCY5ackI5yycvKHr9I,365
|
5
|
+
todo_agent/core/conversation_manager.py,sha256=9aAWogswZe9Cs7wKT47RG-cLh1LQ5D9RbhUHJVUyTS8,13549
|
6
|
+
todo_agent/core/exceptions.py,sha256=ilVL5hyPHGYQXsLm0pYivMbhbamupG77r8TbiQr2tAU,2034
|
7
|
+
todo_agent/core/todo_manager.py,sha256=nLQbri-hhbqtV-zJNpdnBHPiUYd7tAcqpFFLX-xxeW4,19263
|
8
|
+
todo_agent/infrastructure/__init__.py,sha256=SGbHXgzq6U1DMgOfWPMsWEK99zjPSF-6gzy7xqc5fsI,284
|
9
|
+
todo_agent/infrastructure/calendar_utils.py,sha256=8tOZHN5CNrRHpTQHYzskmsZNJKWuUFjrjyvHQ76AhzU,1837
|
10
|
+
todo_agent/infrastructure/config.py,sha256=zyp6qOlg1nN_awphivlgGNBE6fL0Hf66YgvWxR8ldyQ,2117
|
11
|
+
todo_agent/infrastructure/inference.py,sha256=tAAv6kou9ucgLaX2f3ZHahjAn2ofB0WQFVUgFVRQ-KM,16193
|
12
|
+
todo_agent/infrastructure/llm_client.py,sha256=Knb6yvQt0q8mobnwVH08mGCd8xBHK_g1S66DOdp7cO8,9807
|
13
|
+
todo_agent/infrastructure/llm_client_factory.py,sha256=-tktnVOIF7B45WR7AuLoi7MKnEyuM8lgg1jjc4T1FhM,1929
|
14
|
+
todo_agent/infrastructure/logger.py,sha256=2ykG_0lyzmEGxDF6ZRl1qiTUGDuFeQgzv4Na6vRmXcM,4110
|
15
|
+
todo_agent/infrastructure/ollama_client.py,sha256=V_zAeBjIEzB8PZXyzFeiLMLA5qf3y4WV2_6Vqxn1ujc,5629
|
16
|
+
todo_agent/infrastructure/openrouter_client.py,sha256=0pxIKvHItwVijFz8l4loOeCa4HUpMvTYROcwYJh3iyI,6748
|
17
|
+
todo_agent/infrastructure/todo_shell.py,sha256=EySpPNZUZ0Dl_-snieKk6TcMx8NHkZvxXz6E-m6RKhI,18201
|
18
|
+
todo_agent/infrastructure/token_counter.py,sha256=PCKheOVJbp1s89yhh_i6iKgURMt9mVoYkwjQJCc2xCE,4958
|
19
|
+
todo_agent/infrastructure/prompts/system_prompt.txt,sha256=APyGy3sjc2LP4YgcxMuC7_9c4BVYzSEb9R8pHobB5H8,4453
|
20
|
+
todo_agent/interface/__init__.py,sha256=vDD3rQu4qDkpvVwGVtkDzE1M4IiSHYzTif4GbYSFWaI,457
|
21
|
+
todo_agent/interface/cli.py,sha256=Ikh5AYoOZnNUVXzHjfv2bsE33yK3VhCdzza83HOzFp8,16460
|
22
|
+
todo_agent/interface/formatters.py,sha256=a9PW-5DbY8K5QYZjBXFZSdzlCmy263kwBI9nXgP8LXI,20081
|
23
|
+
todo_agent/interface/progress.py,sha256=EpPa20Hrnjv_TBIp0tzViVciThqsOvAHuYO4v2rc8eI,1751
|
24
|
+
todo_agent/interface/tools.py,sha256=-pMPSNKyb5M5CGie6-ZxDYdl82YAt0v9ie3TCILOfQg,51714
|
25
|
+
todo_agent-0.3.3.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
26
|
+
todo_agent-0.3.3.dist-info/METADATA,sha256=VceLOqM8POJIJxGuRsAalzTMgnilLCuL7vmdJgbNOlU,10056
|
27
|
+
todo_agent-0.3.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
28
|
+
todo_agent-0.3.3.dist-info/entry_points.txt,sha256=4W7LrCib6AXP5IZDwWRht8S5gutLu5oNfTJHGbt4oHs,52
|
29
|
+
todo_agent-0.3.3.dist-info/top_level.txt,sha256=a65mlPIhPZHuq2bRIi_sCMAIJsUddvXt171OBF6r6co,11
|
30
|
+
todo_agent-0.3.3.dist-info/RECORD,,
|