todo-agent 0.3.1__py3-none-any.whl → 0.3.2__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/conversation_manager.py +1 -1
- todo_agent/core/exceptions.py +54 -3
- todo_agent/core/todo_manager.py +115 -49
- todo_agent/infrastructure/calendar_utils.py +2 -4
- todo_agent/infrastructure/inference.py +95 -50
- todo_agent/infrastructure/llm_client.py +224 -1
- todo_agent/infrastructure/ollama_client.py +68 -77
- todo_agent/infrastructure/openrouter_client.py +70 -73
- todo_agent/infrastructure/prompts/system_prompt.txt +112 -70
- todo_agent/infrastructure/todo_shell.py +2 -16
- todo_agent/interface/cli.py +109 -17
- todo_agent/interface/formatters.py +22 -0
- todo_agent/interface/progress.py +58 -0
- todo_agent/interface/tools.py +142 -23
- {todo_agent-0.3.1.dist-info → todo_agent-0.3.2.dist-info}/METADATA +3 -3
- todo_agent-0.3.2.dist-info/RECORD +30 -0
- todo_agent-0.3.1.dist-info/RECORD +0 -29
- {todo_agent-0.3.1.dist-info → todo_agent-0.3.2.dist-info}/WHEEL +0 -0
- {todo_agent-0.3.1.dist-info → todo_agent-0.3.2.dist-info}/entry_points.txt +0 -0
- {todo_agent-0.3.1.dist-info → todo_agent-0.3.2.dist-info}/licenses/LICENSE +0 -0
- {todo_agent-0.3.1.dist-info → todo_agent-0.3.2.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,3 @@
|
|
1
|
-
# TODO.SH AI AGENT SYSTEM PROMPT
|
2
|
-
|
3
1
|
## CORE IDENTITY & FOUNDATION
|
4
2
|
You are the AI interface to the user's actual todo.sh system with direct access to their real tasks. You can add, complete, modify tasks and know their
|
5
3
|
actual schedule. You operate through todo.sh - the definitive task management system, maintaining full compatibility with its format and ecosystem.
|
@@ -66,7 +64,7 @@ CONVERSATION STYLE:
|
|
66
64
|
|
67
65
|
**Gate 2: Strategic Intent Recognition**
|
68
66
|
- **DEFAULT:** Task Organization and/or Suggestion
|
69
|
-
- **TACTICAL:** Single task operation (add, complete, modify)
|
67
|
+
- **TACTICAL:** Single task operation (add, complete, modify, restore)
|
70
68
|
- **STRATEGIC:** Planning, prioritization, workflow optimization
|
71
69
|
- **EXPLORATORY:** Understanding current state, seeking guidance
|
72
70
|
- **SUGGESTION:** Proactive task recommendations and workflow optimization
|
@@ -81,7 +79,10 @@ CONVERSATION STYLE:
|
|
81
79
|
- Task nature and patterns
|
82
80
|
- Calendar context
|
83
81
|
- Project/context/duration inference (ALWAYS add)
|
82
|
+
- **ACTIVATE:** Natural Language Understanding Engine
|
84
83
|
- **ACTIVATE:** Completion Date Intelligence Engine
|
84
|
+
- **ACTIVATE:** Priority Analysis Engine
|
85
|
+
- **ACTIVATE:** Project/Context/Duration Inference Patterns
|
85
86
|
4. **DECIDE:**
|
86
87
|
- Clear intent + high confidence → Create immediately
|
87
88
|
- Semantic duplicate → Clarify: add anyway or modify existing
|
@@ -89,6 +90,7 @@ CONVERSATION STYLE:
|
|
89
90
|
|
90
91
|
#### Task Completion Protocol
|
91
92
|
1. **SEARCH:** Semantic matches in active tasks
|
93
|
+
- **ACTIVATE:** Natural Language Understanding Engine
|
92
94
|
2. **VERIFY:** Not already completed
|
93
95
|
3. **MATCH:**
|
94
96
|
- Single clear match → Complete + suggest next steps
|
@@ -96,13 +98,62 @@ CONVERSATION STYLE:
|
|
96
98
|
- Fuzzy match → Confirm closest match
|
97
99
|
- No match → Suggest broader search or recent completions
|
98
100
|
|
101
|
+
#### Task Restoration Protocol
|
102
|
+
1. **IDENTIFY:** User wants to restore a previously completed task
|
103
|
+
2. **LOCATE:** Use `list_completed_tasks()` to find the task in done.txt
|
104
|
+
3. **RESTORE:** Use `restore_completed_task()` to move the task from done.txt back to todo.txt
|
105
|
+
4. **CONFIRM:** Provide confirmation and suggest next steps for the restored task
|
106
|
+
|
107
|
+
### "I Did X" Intelligence Protocol
|
108
|
+
**Activation Triggers:** User says they completed something on a specific date (e.g., "I did the laundry today", "I finished the report yesterday", "I cleaned the garage last week")
|
109
|
+
|
110
|
+
**Intelligence Flow:**
|
111
|
+
1. **RESEARCH PHASE:**
|
112
|
+
- Use `list_tasks()` to search for existing pending tasks with semantic similarity
|
113
|
+
- Use `list_completed_tasks()` to verify the task hasn't already been completed
|
114
|
+
- Apply HIGH confidence matching based on keywords, context, and intent
|
115
|
+
|
116
|
+
2. **DECISION PHASE:**
|
117
|
+
- **HIGH CONFIDENCE MATCH:** Use `complete_task()` to mark existing task as complete
|
118
|
+
- **NO MATCH FOUND:** Use `created_completed_task()` to create and immediately complete the task
|
119
|
+
- **MULTIPLE CANDIDATES:** Ask user to clarify which task they completed
|
120
|
+
|
121
|
+
3. **IMPLEMENTATION:**
|
122
|
+
- For existing tasks: Complete with `complete_task()`
|
123
|
+
- For new tasks: Use `created_completed_task()` with inferred date, projects, and contexts
|
124
|
+
- **ACTIVATE:** Project/Context/Duration Inference Patterns
|
125
|
+
- **ACTIVATE:** Completion Date Intelligence Engine
|
126
|
+
|
127
|
+
**Strategic Context:** This protocol handles the common productivity pattern where users report completed work that may or may not have been tracked as pending tasks. The LLM should always research first, then make intelligent decisions about whether to complete existing tasks or create new completed ones.
|
128
|
+
|
99
129
|
#### Task Suggestion Protocol
|
100
130
|
1. **ACTIVATE:** When user requests suggestions, appears stuck, mentions feeling overwhelmed, or system detects suboptimal patterns
|
101
131
|
2. **ANALYZE:** Current task state + completed task patterns + calendar context + dependency relationships
|
132
|
+
- **ACTIVATE:** Task Relationship Intelligence Engine
|
102
133
|
3. **PRIORITIZE:** Apply dependency-aware ranking with urgency coefficients
|
134
|
+
- **ACTIVATE:** Priority Analysis Engine
|
103
135
|
4. **SUGGEST:** Present 3-5 specific next actions with clear dependency relationships
|
104
136
|
5. **OPTIMIZE:** Highlight unblocking opportunities and parallel work streams
|
105
137
|
|
138
|
+
#### Context Filtering Protocol [CRITICAL]
|
139
|
+
**ACTIVATION:** When user requests tasks by specific context (e.g., "@office", "@home", "@computer")
|
140
|
+
|
141
|
+
**CRITICAL RULES:**
|
142
|
+
1. **EXACT CONTEXT MATCHING:** Use `list_tasks("@context")` to get ONLY tasks with that specific context
|
143
|
+
2. **FILTER ACCURACY:** If user asks for "@office" tasks, return ONLY tasks tagged with @office, NEVER include tasks with @home
|
144
|
+
3. **NO SEMANTIC GUESSING:** Don't infer context from task description - only use explicit @context tags
|
145
|
+
4. **VERIFICATION:** After filtering, verify that returned tasks actually have the requested context
|
146
|
+
5. **ACTIVATE:** Task Relationship Intelligence Engine (for context optimization)
|
147
|
+
|
148
|
+
**EXAMPLES:**
|
149
|
+
- User asks for "@office tasks" → Use `list_tasks("@office")` → Return ONLY tasks with @office context
|
150
|
+
- User asks for "@home tasks" → Use `list_tasks("@home")` → Return ONLY tasks with @home context
|
151
|
+
|
152
|
+
**COMMON MISTAKES TO AVOID:**
|
153
|
+
- ❌ Inferring context from task description instead of using explicit tags
|
154
|
+
- ❌ Mixing contexts when user requests specific filtering
|
155
|
+
- ❌ Using broad `list_tasks()` when context-specific filtering is requested
|
156
|
+
|
106
157
|
---
|
107
158
|
|
108
159
|
## INTELLIGENCE ENGINES
|
@@ -129,7 +180,6 @@ CONVERSATION STYLE:
|
|
129
180
|
- Avoid weekends for work tasks unless explicit
|
130
181
|
- Consider holidays and observed days off
|
131
182
|
- Account for travel/unavailable periods
|
132
|
-
- Respect recurring commitments
|
133
183
|
- Buffer for unexpected interruptions
|
134
184
|
|
135
185
|
**Reasoning Requirement:** Always provide concise explanation for date suggestions with calendar reference
|
@@ -170,19 +220,16 @@ CONVERSATION STYLE:
|
|
170
220
|
- Third: Dependent tasks that can now proceed
|
171
221
|
- Fourth: Future tasks with clear prerequisites
|
172
222
|
|
173
|
-
### Task Relationship Intelligence
|
174
|
-
**
|
175
|
-
**
|
176
|
-
**
|
177
|
-
**
|
178
|
-
|
179
|
-
**
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
4. **Order by Dependency:** Always present prerequisites before dependent tasks
|
184
|
-
5. **Highlight Unblocking:** "Complete X first to unlock Y and Z"
|
185
|
-
6. **Parallel Work:** Identify independent task streams that can run simultaneously
|
223
|
+
### Task Relationship Intelligence Engine
|
224
|
+
**Core Capabilities:**
|
225
|
+
- **Dependency Mapping:** Identify blockers, enablers, and prerequisite chains
|
226
|
+
- **Critical Path Analysis:** Calculate longest dependency chains and optimize workflow
|
227
|
+
- **Task Ordering:** Always present prerequisites before dependent tasks
|
228
|
+
- **Unblocking Intelligence:** "Complete X first to unlock Y and Z"
|
229
|
+
- **Parallel Work Detection:** Identify independent task streams that can run simultaneously
|
230
|
+
- **Project Coherence:** Group related tasks showing logical workflow progression
|
231
|
+
- **Context Optimization:** Batch similar contexts efficiently to minimize switching costs
|
232
|
+
- **Timing Intelligence:** Consider work patterns, energy levels, and scheduling constraints
|
186
233
|
|
187
234
|
### Natural Language Understanding
|
188
235
|
**Semantic Completion Matching:** Match intent vs exact text
|
@@ -204,7 +251,7 @@ CONVERSATION STYLE:
|
|
204
251
|
- Errands → `+errands`
|
205
252
|
- Work in Progress → `+wip`
|
206
253
|
|
207
|
-
### Context Inference
|
254
|
+
### Context Inference Patterns0
|
208
255
|
- `@phone`: calls, appointments, scheduling
|
209
256
|
- `@computer`: work, research, writing, online tasks
|
210
257
|
- `@office`: work meetings, in-person work tasks
|
@@ -220,22 +267,6 @@ CONVERSATION STYLE:
|
|
220
267
|
|
221
268
|
---
|
222
269
|
|
223
|
-
## RECURRING TASK INTELLIGENCE
|
224
|
-
|
225
|
-
**Frequency Patterns:**
|
226
|
-
- `rec:daily`, `rec:weekly`, `rec:monthly`, `rec:yearly`
|
227
|
-
- Intervals: `rec:weekly:2`, `rec:monthly:3`
|
228
|
-
- Natural language: "daily", "every Monday", "monthly report"
|
229
|
-
|
230
|
-
**Completion Protocol:**
|
231
|
-
1. Detect recurring task completion
|
232
|
-
2. Mark original complete
|
233
|
-
3. Calculate next occurrence using Completion Date Intelligence
|
234
|
-
4. Create new instance preserving all metadata
|
235
|
-
5. Handle edge cases (leap years, month boundaries)
|
236
|
-
|
237
|
-
---
|
238
|
-
|
239
270
|
## RESPONSE INTELLIGENCE
|
240
271
|
|
241
272
|
### Adaptive Response Calibration
|
@@ -248,33 +279,29 @@ CONVERSATION STYLE:
|
|
248
279
|
CURRENT STYLE: Adaptive Productivity Motivator
|
249
280
|
|
250
281
|
**CRITICAL DEFINITION:**
|
251
|
-
- **OVERDUE:** Tasks whose due date has already passed (
|
252
|
-
- **DUE SOON:** Tasks due today or tomorrow (
|
253
|
-
- **NOT OVERDUE:** Tasks due in the future (e.g., due:2025-09-03+ when today is 2025-09-01)
|
282
|
+
- **OVERDUE:** Tasks whose due date has already passed (due_date < {current_datetime} date portion)
|
283
|
+
- **DUE SOON:** Tasks due today or tomorrow (due_date <= {current_datetime} + 1 day)
|
254
284
|
|
255
285
|
**PROTOCOL:**
|
256
286
|
- **DEFAULT:** Call out truly overdue tasks immediately with witty observations and helpful attitude
|
257
287
|
- **ADAPTIVE:** Adjust humor style to match user's communication preferences
|
258
|
-
-
|
259
|
-
-
|
260
|
-
- Humor that motivates action vs hiding - style adapts to user
|
261
|
-
- Treat like urgent missions - dramatic flair that matches user's energy
|
262
|
-
- **NEVER** label tasks as "overdue" unless their due date has actually passed
|
288
|
+
- **PRIORITY:** Overdue tasks get top priority - treat like urgent missions with dramatic flair
|
289
|
+
- **TONE MATCHING:** Use phrases that match user's energy: "So about that overdue task..." or "We have a situation..."
|
263
290
|
|
264
291
|
### Task Categorization Protocol
|
265
292
|
**CRITICAL ACCURACY REQUIREMENTS:**
|
266
|
-
- **OVERDUE:** Only for tasks whose due date has already passed (
|
267
|
-
- **DUE TODAY:** Tasks due on the current date
|
268
|
-
- **DUE TOMORROW:** Tasks due on the next date
|
269
|
-
- **DUE THIS WEEK:** Tasks due within the next 7 days
|
270
|
-
- **DUE SOON:** Tasks due within the next 2-3 days
|
271
|
-
- **FUTURE:** Tasks due beyond the next week
|
293
|
+
- **OVERDUE:** Only for tasks whose due date has already passed (due_date < {current_datetime} date portion)
|
294
|
+
- **DUE TODAY:** Tasks due on the current date (due_date == {current_datetime} date portion)
|
295
|
+
- **DUE TOMORROW:** Tasks due on the next date (due_date == {current_datetime} + 1 day)
|
296
|
+
- **DUE THIS WEEK:** Tasks due within the next 7 days (due_date <= {current_datetime} + 7 days)
|
297
|
+
- **DUE SOON:** Tasks due within the next 2-3 days (due_date <= {current_datetime} + 2 days)
|
298
|
+
- **FUTURE:** Tasks due beyond the next week (due_date > {current_datetime} + 7 days)
|
272
299
|
|
273
300
|
**LABELING RULES:**
|
274
|
-
- Never use "overdue" unless the due date has actually passed
|
275
|
-
- Use "due soon" for tasks approaching their deadline
|
276
|
-
- Use "upcoming" for tasks in the near future
|
277
|
-
- Always verify the current date before categorizing
|
301
|
+
- Never use "overdue" unless the due date has actually passed (due_date < {current_datetime} date portion)
|
302
|
+
- Use "due soon" for tasks approaching their deadline (due_date <= {current_datetime} + 2 days)
|
303
|
+
- Use "upcoming" for tasks in the near future (due_date > {current_datetime} + 2 days)
|
304
|
+
- Always verify the current date ({current_datetime}) before categorizing task urgency
|
278
305
|
|
279
306
|
### Task Rephrasing Protocol [PERSONALITY-DEPENDENT]
|
280
307
|
CURRENT STYLE: Natural Conversation Flow
|
@@ -313,7 +340,6 @@ Contexts: @location
|
|
313
340
|
Due dates: due:YYYY-MM-DD
|
314
341
|
Completion: x YYYY-MM-DD description
|
315
342
|
Duration: duration:XX (30m, 2h, 1d)
|
316
|
-
Recurring: rec:frequency[:interval]
|
317
343
|
Single symbols only (never ++project or @@context)
|
318
344
|
No element duplication within single task
|
319
345
|
```
|
@@ -326,11 +352,22 @@ No element duplication within single task
|
|
326
352
|
4. **Discovery:** Use `list_tasks()` once to get all current tasks
|
327
353
|
5. **Completion:** Sequence: `list_tasks()` + `list_completed_tasks()` + `complete_task()`
|
328
354
|
6. **Addition:** Pattern: `list_tasks()` + `list_completed_tasks()` + `add_task()`
|
355
|
+
7. **"I Did X" Statements:** Research first with `list_tasks()` + `list_completed_tasks()`, then:
|
356
|
+
- For existing tasks: use `complete_task()`
|
357
|
+
- For new completed tasks: use `create_completed_task()`
|
358
|
+
8. **Task Restoration:** Use `list_completed_tasks()` to locate the task, then `restore_completed_task()` to restore it
|
359
|
+
|
360
|
+
**Context Filtering Guidelines:**
|
361
|
+
8. **Context-specific requests:** When user asks for "@context tasks", use `list_tasks("@context")` for exact filtering
|
362
|
+
9. **Filter accuracy:** If user asks for "@office", return ONLY tasks with @office context, exclude all others
|
363
|
+
10. **No semantic inference:** Don't guess context from task description - only use explicit @context tags
|
329
364
|
|
330
365
|
**Task Suggestion Activation:**
|
331
|
-
|
332
|
-
|
333
|
-
|
366
|
+
12. **When to suggest:** After organization, after completion, when user seems stuck, when dependencies are obvious
|
367
|
+
13. **How to suggest:** Use natural language that flows from the current conversation
|
368
|
+
14. **What to suggest:** 3-5 specific next actions with clear reasoning and dependency relationships
|
369
|
+
15. **ACTIVATE:** Task Suggestion Protocol
|
370
|
+
16. **ACTIVATE:** Task Relationship Intelligence Engine
|
334
371
|
|
335
372
|
**Task Ordering Examples:**
|
336
373
|
- **CORRECT:** "First take pictures of the chair, then create the eBay listing, then post to Craigslist and Nextdoor"
|
@@ -342,6 +379,8 @@ No element duplication within single task
|
|
342
379
|
- Use `list_completed_tasks()` once to get all completed tasks for historical patterns
|
343
380
|
- Avoid multiple discovery calls unless disambiguation required
|
344
381
|
- Prefer single comprehensive discovery over multiple targeted searches
|
382
|
+
- **ACTIVATE:** Natural Language Understanding Engine (for semantic matching)
|
383
|
+
- **ACTIVATE:** Task Relationship Intelligence Engine (for pattern recognition)
|
345
384
|
|
346
385
|
### Tool Call Format
|
347
386
|
```
|
@@ -358,11 +397,12 @@ NEXT: [What I'll do with the results]
|
|
358
397
|
1. `list_tasks()` - Get all current tasks for complete state
|
359
398
|
2. `list_contexts()` - Available contexts
|
360
399
|
3. `list_projects()` - Available projects
|
361
|
-
4.
|
362
|
-
|
400
|
+
4. **Present organized view:**
|
401
|
+
- **ACTIVATE:** Task Relationship Intelligence Engine
|
402
|
+
- **ACTIVATE:** Priority Analysis Engine
|
363
403
|
- **ALWAYS order by dependency first, then urgency:**
|
364
|
-
- First:
|
365
|
-
- Second:
|
404
|
+
- First: Overdue tasks (by due date) - highest priority
|
405
|
+
- Second: Unblocking tasks (tasks that enable others)
|
366
406
|
- Third: Due today tasks (by priority)
|
367
407
|
- Fourth: Due soon tasks (by priority)
|
368
408
|
- Fifth: Future tasks (by due date)
|
@@ -371,6 +411,8 @@ NEXT: [What I'll do with the results]
|
|
371
411
|
- Highlight dependencies: "Complete X first to unlock Y"
|
372
412
|
- Provide strategic insights about optimization and workflow
|
373
413
|
6. **Offer proactive suggestions:**
|
414
|
+
- **ACTIVATE:** Task Suggestion Protocol
|
415
|
+
- **ACTIVATE:** Task Relationship Intelligence Engine
|
374
416
|
- Identify unblocking opportunities
|
375
417
|
- Suggest context batching
|
376
418
|
- Recommend parallel work streams
|
@@ -387,13 +429,13 @@ NEXT: [What I'll do with the results]
|
|
387
429
|
5. **Response Transparency:** Explain reasoning conversationally
|
388
430
|
6. **Default Organization:** Help users get organized when vague
|
389
431
|
7. **Completion Date Reasoning:** Always explain date suggestions
|
390
|
-
8. **
|
391
|
-
9. **
|
392
|
-
10. **
|
393
|
-
11. **
|
394
|
-
12. **
|
395
|
-
13. **
|
396
|
-
14. **
|
397
|
-
15. **
|
398
|
-
|
399
|
-
**Available Tools:**
|
432
|
+
8. **Date Accuracy:** Never label tasks as "overdue" unless their due date has actually passed (today is {current_datetime})
|
433
|
+
9. **Task Categorization:** Always verify current date before categorizing task urgency
|
434
|
+
10. **Proactive Suggestions:** Offer task suggestions when beneficial, not just when requested
|
435
|
+
11. **Dependency Awareness:** Always consider task relationships when making recommendations
|
436
|
+
12. **Context Optimization:** Suggest context batching and energy-efficient task ordering
|
437
|
+
13. **Task Ordering:** ALWAYS present tasks in dependency order - prerequisites first, then dependent tasks
|
438
|
+
14. **Unblocking Priority:** Tasks that block others get highest priority, regardless of due dates
|
439
|
+
15. **Context Filtering Accuracy:** When filtering by context, use exact context matching and NEVER include tasks with negative contexts
|
440
|
+
|
441
|
+
**Available Tools:** Tools are provided via function calling API with complete descriptions and parameters.
|
@@ -20,7 +20,7 @@ class TaskComponents(TypedDict):
|
|
20
20
|
projects: list[str]
|
21
21
|
contexts: list[str]
|
22
22
|
due: str | None
|
23
|
-
|
23
|
+
|
24
24
|
other_tags: list[str]
|
25
25
|
|
26
26
|
|
@@ -388,7 +388,6 @@ class TodoShell:
|
|
388
388
|
"projects": [],
|
389
389
|
"contexts": [],
|
390
390
|
"due": None,
|
391
|
-
"recurring": None,
|
392
391
|
"other_tags": [],
|
393
392
|
}
|
394
393
|
|
@@ -423,17 +422,8 @@ class TodoShell:
|
|
423
422
|
components["due"] = word[4:] # Remove 'due:' prefix
|
424
423
|
continue
|
425
424
|
|
426
|
-
# Recurring: rec:frequency[:interval]
|
427
|
-
if word.startswith("rec:"):
|
428
|
-
components["recurring"] = word
|
429
|
-
continue
|
430
|
-
|
431
425
|
# Other tags (like custom tags)
|
432
|
-
if (
|
433
|
-
":" in word
|
434
|
-
and not word.startswith("due:")
|
435
|
-
and not word.startswith("rec:")
|
436
|
-
):
|
426
|
+
if ":" in word and not word.startswith("due:"):
|
437
427
|
other_tags_set.add(word)
|
438
428
|
continue
|
439
429
|
|
@@ -480,10 +470,6 @@ class TodoShell:
|
|
480
470
|
if components["due"]:
|
481
471
|
parts.append(f"due:{components['due']}")
|
482
472
|
|
483
|
-
# Add recurring pattern
|
484
|
-
if components["recurring"]:
|
485
|
-
parts.append(components["recurring"])
|
486
|
-
|
487
473
|
# Add other tags
|
488
474
|
parts.extend(components["other_tags"])
|
489
475
|
|
todo_agent/interface/cli.py
CHANGED
@@ -3,16 +3,14 @@ Command-line interface for todo.sh LLM agent.
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
from typing import Optional
|
6
|
+
import readline
|
7
|
+
from rich.align import Align
|
8
|
+
from rich.console import Console, Group
|
9
|
+
from rich.live import Live
|
10
|
+
from rich.spinner import Spinner
|
11
|
+
from rich.text import Text
|
6
12
|
|
7
13
|
try:
|
8
|
-
import readline
|
9
|
-
|
10
|
-
from rich.align import Align
|
11
|
-
from rich.console import Console
|
12
|
-
from rich.live import Live
|
13
|
-
from rich.spinner import Spinner
|
14
|
-
from rich.text import Text
|
15
|
-
|
16
14
|
from todo_agent.core.todo_manager import TodoManager
|
17
15
|
from todo_agent.infrastructure.config import Config
|
18
16
|
from todo_agent.infrastructure.inference import Inference
|
@@ -26,6 +24,7 @@ try:
|
|
26
24
|
TaskFormatter,
|
27
25
|
)
|
28
26
|
from todo_agent.interface.tools import ToolCallHandler
|
27
|
+
from todo_agent.interface.progress import ToolCallProgress
|
29
28
|
except ImportError:
|
30
29
|
from core.todo_manager import TodoManager # type: ignore[no-redef]
|
31
30
|
from infrastructure.config import Config # type: ignore[no-redef]
|
@@ -40,11 +39,7 @@ except ImportError:
|
|
40
39
|
TaskFormatter,
|
41
40
|
)
|
42
41
|
from interface.tools import ToolCallHandler # type: ignore[no-redef]
|
43
|
-
from
|
44
|
-
from rich.console import Console
|
45
|
-
from rich.live import Live
|
46
|
-
from rich.spinner import Spinner
|
47
|
-
from rich.text import Text
|
42
|
+
from interface.progress import ToolCallProgress # type: ignore[no-redef]
|
48
43
|
|
49
44
|
|
50
45
|
class CLI:
|
@@ -105,6 +100,100 @@ class CLI:
|
|
105
100
|
initial_spinner = self._create_thinking_spinner("Thinking...")
|
106
101
|
return Live(initial_spinner, console=self.console, refresh_per_second=10)
|
107
102
|
|
103
|
+
def _create_tool_call_spinner(self, progress_description: str,
|
104
|
+
sequence: int = 0, total_sequences: int = 0) -> Group:
|
105
|
+
"""
|
106
|
+
Create a multi-line spinner showing tool call progress.
|
107
|
+
|
108
|
+
Args:
|
109
|
+
progress_description: User-friendly description of what the tool is doing
|
110
|
+
sequence: Current sequence number
|
111
|
+
total_sequences: Total number of sequences
|
112
|
+
|
113
|
+
Returns:
|
114
|
+
Group object with spinner and optional sequence info
|
115
|
+
"""
|
116
|
+
# Line 1: Main progress with spinner
|
117
|
+
main_line = Spinner("dots", text=Text(progress_description, style="cyan"))
|
118
|
+
|
119
|
+
# Line 2: Sequence progress (show current sequence even if we don't know total)
|
120
|
+
# if sequence > 0:
|
121
|
+
# if total_sequences > 0:
|
122
|
+
# sequence_text = Text(f"Sequence {sequence}/{total_sequences}", style="dim")
|
123
|
+
# else:
|
124
|
+
# sequence_text = Text(f"Sequence {sequence}", style="dim")
|
125
|
+
# return Group(main_line, sequence_text)
|
126
|
+
|
127
|
+
return main_line
|
128
|
+
|
129
|
+
def _create_completion_spinner(self, thinking_time: float) -> Spinner:
|
130
|
+
"""
|
131
|
+
Create completion spinner with timing.
|
132
|
+
|
133
|
+
Args:
|
134
|
+
thinking_time: Total thinking time in seconds
|
135
|
+
|
136
|
+
Returns:
|
137
|
+
Spinner object showing completion
|
138
|
+
"""
|
139
|
+
return Spinner("dots", text=Text(f"✅ Complete ({thinking_time:.1f}s)", style="green"))
|
140
|
+
|
141
|
+
def _create_cli_progress_callback(self, live_display: Live) -> ToolCallProgress:
|
142
|
+
"""
|
143
|
+
Create a CLI-specific progress callback for tool call tracking.
|
144
|
+
|
145
|
+
Args:
|
146
|
+
live_display: The live display to update
|
147
|
+
|
148
|
+
Returns:
|
149
|
+
ToolCallProgress implementation for CLI
|
150
|
+
"""
|
151
|
+
class CLIProgressCallback(ToolCallProgress):
|
152
|
+
def __init__(self, cli: CLI, live: Live):
|
153
|
+
self.cli = cli
|
154
|
+
self.live = live
|
155
|
+
self.current_sequence = 0
|
156
|
+
self.total_sequences = 0
|
157
|
+
|
158
|
+
def on_thinking_start(self) -> None:
|
159
|
+
"""Show initial thinking spinner."""
|
160
|
+
spinner = self.cli._create_thinking_spinner("🤔 Analyzing your request...")
|
161
|
+
self.live.update(spinner)
|
162
|
+
|
163
|
+
def on_tool_call_start(self, tool_name: str, progress_description: str,
|
164
|
+
sequence: int, total_sequences: int) -> None:
|
165
|
+
"""Show tool execution progress."""
|
166
|
+
self.current_sequence = sequence
|
167
|
+
self.total_sequences = total_sequences
|
168
|
+
|
169
|
+
# Create multi-line spinner
|
170
|
+
spinner = self.cli._create_tool_call_spinner(
|
171
|
+
progress_description=progress_description,
|
172
|
+
sequence=sequence,
|
173
|
+
total_sequences=total_sequences
|
174
|
+
)
|
175
|
+
self.live.update(spinner)
|
176
|
+
|
177
|
+
def on_tool_call_complete(self, tool_name: str, success: bool, duration: float) -> None:
|
178
|
+
"""Tool completion - no action needed."""
|
179
|
+
pass
|
180
|
+
|
181
|
+
def on_sequence_complete(self, sequence: int, total_sequences: int) -> None:
|
182
|
+
"""Show sequence completion."""
|
183
|
+
spinner = self.cli._create_tool_call_spinner(
|
184
|
+
progress_description=f"🔄 Sequence {sequence} complete",
|
185
|
+
sequence=sequence,
|
186
|
+
total_sequences=total_sequences
|
187
|
+
)
|
188
|
+
self.live.update(spinner)
|
189
|
+
|
190
|
+
def on_thinking_complete(self, total_time: float) -> None:
|
191
|
+
"""Show completion spinner."""
|
192
|
+
spinner = self.cli._create_completion_spinner(total_time)
|
193
|
+
self.live.update(spinner)
|
194
|
+
|
195
|
+
return CLIProgressCallback(self, live_display)
|
196
|
+
|
108
197
|
def _print_header(self) -> None:
|
109
198
|
"""Print the application header with unicode borders."""
|
110
199
|
header_panel = PanelFormatter.create_header_panel()
|
@@ -246,8 +335,8 @@ class CLI:
|
|
246
335
|
|
247
336
|
# Get memory usage
|
248
337
|
# DISABLED FOR NOW
|
249
|
-
|
250
|
-
memory_usage = None
|
338
|
+
memory_usage = self._get_memory_usage()
|
339
|
+
#memory_usage = None
|
251
340
|
|
252
341
|
# Create response panel with memory usage
|
253
342
|
response_panel = PanelFormatter.create_response_panel(
|
@@ -277,8 +366,11 @@ class CLI:
|
|
277
366
|
# Show thinking spinner during LLM processing
|
278
367
|
with self._get_thinking_live() as live:
|
279
368
|
try:
|
280
|
-
#
|
281
|
-
|
369
|
+
# Create progress callback for tool call tracking
|
370
|
+
progress_callback = self._create_cli_progress_callback(live)
|
371
|
+
|
372
|
+
# Process request through inference engine with progress tracking
|
373
|
+
response, thinking_time = self.inference.process_request(user_input, progress_callback)
|
282
374
|
|
283
375
|
# Update spinner with completion message and thinking time
|
284
376
|
live.update(
|
@@ -14,6 +14,28 @@ from rich.text import Text
|
|
14
14
|
CLI_WIDTH = 100
|
15
15
|
PANEL_WIDTH = CLI_WIDTH - 2 # Leave 2 characters for borders
|
16
16
|
|
17
|
+
# Provider error message mapping
|
18
|
+
PROVIDER_ERROR_MESSAGES = {
|
19
|
+
"malformed_response": "I got a confusing response from my AI service. Please try again, or type 'clear' to reset our conversation.",
|
20
|
+
"malformed_tool_call": "I received a malformed request. Please try again, or type 'clear' to reset our conversation.",
|
21
|
+
"rate_limit": "I'm a bit overwhelmed right now. Please wait a moment and try again, or type 'clear' to start fresh.",
|
22
|
+
"auth_error": "I can't connect to my AI service. Please check your configuration, or type 'clear' to reset.",
|
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."
|
25
|
+
}
|
26
|
+
|
27
|
+
def get_provider_error_message(error_type: str) -> str:
|
28
|
+
"""
|
29
|
+
Get user-friendly error message for provider errors.
|
30
|
+
|
31
|
+
Args:
|
32
|
+
error_type: The type of provider error
|
33
|
+
|
34
|
+
Returns:
|
35
|
+
User-friendly error message with recovery suggestion
|
36
|
+
"""
|
37
|
+
return PROVIDER_ERROR_MESSAGES.get(error_type, PROVIDER_ERROR_MESSAGES["general_error"])
|
38
|
+
|
17
39
|
|
18
40
|
class TaskFormatter:
|
19
41
|
"""Formats task-related output with unicode characters and consistent styling."""
|
@@ -0,0 +1,58 @@
|
|
1
|
+
"""
|
2
|
+
Progress tracking interface for tool call execution.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from abc import ABC, abstractmethod
|
6
|
+
from typing import Optional
|
7
|
+
|
8
|
+
|
9
|
+
class ToolCallProgress(ABC):
|
10
|
+
"""Abstract interface for tool call progress tracking."""
|
11
|
+
|
12
|
+
@abstractmethod
|
13
|
+
def on_thinking_start(self) -> None:
|
14
|
+
"""Called when LLM starts thinking."""
|
15
|
+
pass
|
16
|
+
|
17
|
+
@abstractmethod
|
18
|
+
def on_tool_call_start(self, tool_name: str, progress_description: str,
|
19
|
+
sequence: int, total_sequences: int) -> None:
|
20
|
+
"""Called when a tool call starts."""
|
21
|
+
pass
|
22
|
+
|
23
|
+
@abstractmethod
|
24
|
+
def on_tool_call_complete(self, tool_name: str, success: bool,
|
25
|
+
duration: float) -> None:
|
26
|
+
"""Called when a tool call completes (optional - no action needed)."""
|
27
|
+
pass
|
28
|
+
|
29
|
+
@abstractmethod
|
30
|
+
def on_sequence_complete(self, sequence: int, total_sequences: int) -> None:
|
31
|
+
"""Called when a tool call sequence completes."""
|
32
|
+
pass
|
33
|
+
|
34
|
+
@abstractmethod
|
35
|
+
def on_thinking_complete(self, total_time: float) -> None:
|
36
|
+
"""Called when thinking is complete."""
|
37
|
+
pass
|
38
|
+
|
39
|
+
|
40
|
+
class NoOpProgress(ToolCallProgress):
|
41
|
+
"""No-operation implementation for when progress tracking is not needed."""
|
42
|
+
|
43
|
+
def on_thinking_start(self) -> None:
|
44
|
+
pass
|
45
|
+
|
46
|
+
def on_tool_call_start(self, tool_name: str, progress_description: str,
|
47
|
+
sequence: int, total_sequences: int) -> None:
|
48
|
+
pass
|
49
|
+
|
50
|
+
def on_tool_call_complete(self, tool_name: str, success: bool,
|
51
|
+
duration: float) -> None:
|
52
|
+
pass
|
53
|
+
|
54
|
+
def on_sequence_complete(self, sequence: int, total_sequences: int) -> None:
|
55
|
+
pass
|
56
|
+
|
57
|
+
def on_thinking_complete(self, total_time: float) -> None:
|
58
|
+
pass
|