todo-agent 0.2.6__py3-none-any.whl → 0.2.8__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 +57 -6
- todo_agent/core/todo_manager.py +29 -0
- todo_agent/infrastructure/inference.py +23 -1
- todo_agent/infrastructure/ollama_client.py +2 -2
- todo_agent/infrastructure/openrouter_client.py +2 -2
- todo_agent/infrastructure/prompts/system_prompt.txt +334 -160
- todo_agent/interface/cli.py +3 -5
- todo_agent/interface/formatters.py +3 -9
- todo_agent/interface/tools.py +162 -7
- {todo_agent-0.2.6.dist-info → todo_agent-0.2.8.dist-info}/METADATA +1 -1
- todo_agent-0.2.8.dist-info/RECORD +29 -0
- todo_agent-0.2.6.dist-info/RECORD +0 -29
- {todo_agent-0.2.6.dist-info → todo_agent-0.2.8.dist-info}/WHEEL +0 -0
- {todo_agent-0.2.6.dist-info → todo_agent-0.2.8.dist-info}/entry_points.txt +0 -0
- {todo_agent-0.2.6.dist-info → todo_agent-0.2.8.dist-info}/licenses/LICENSE +0 -0
- {todo_agent-0.2.6.dist-info → todo_agent-0.2.8.dist-info}/top_level.txt +0 -0
todo_agent/_version.py
CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
28
28
|
commit_id: COMMIT_ID
|
29
29
|
__commit_id__: COMMIT_ID
|
30
30
|
|
31
|
-
__version__ = version = '0.2.
|
32
|
-
__version_tuple__ = version_tuple = (0, 2,
|
31
|
+
__version__ = version = '0.2.8'
|
32
|
+
__version_tuple__ = version_tuple = (0, 2, 8)
|
33
33
|
|
34
34
|
__commit_id__ = commit_id = None
|
@@ -35,14 +35,16 @@ class ConversationManager:
|
|
35
35
|
"""Manages conversation state and memory for LLM interactions."""
|
36
36
|
|
37
37
|
def __init__(
|
38
|
-
self, max_tokens: int =
|
38
|
+
self, max_tokens: int = 32000, max_messages: int = 50, model: str = "gpt-4"
|
39
39
|
):
|
40
40
|
self.history: List[ConversationMessage] = []
|
41
41
|
self.max_tokens = max_tokens
|
42
42
|
self.max_messages = max_messages
|
43
43
|
self.system_prompt: Optional[str] = None
|
44
44
|
self.token_counter = get_token_counter(model)
|
45
|
-
self._total_tokens = 0 # Running total of tokens in conversation
|
45
|
+
self._total_tokens: int = 0 # Running total of tokens in conversation
|
46
|
+
self._tools_tokens: int = 0 # Cache for tools token count
|
47
|
+
self._last_tools_hash: Optional[int] = None # Track if tools have changed
|
46
48
|
|
47
49
|
def add_message(
|
48
50
|
self,
|
@@ -140,10 +142,44 @@ class ConversationManager:
|
|
140
142
|
self._total_tokens -= message.token_count
|
141
143
|
self.history.pop(index)
|
142
144
|
|
143
|
-
def
|
145
|
+
def get_request_tokens(self, tools: List[Dict[str, Any]]) -> int:
|
146
|
+
"""
|
147
|
+
Get total request tokens (conversation + tools).
|
148
|
+
|
149
|
+
Args:
|
150
|
+
tools: Current tool definitions
|
151
|
+
|
152
|
+
Returns:
|
153
|
+
Total request tokens
|
154
|
+
"""
|
155
|
+
# Check if tools have changed
|
156
|
+
tools_hash = hash(str(tools))
|
157
|
+
if tools_hash != self._last_tools_hash:
|
158
|
+
self._tools_tokens = self.token_counter.count_tools_tokens(tools)
|
159
|
+
self._last_tools_hash = tools_hash
|
160
|
+
|
161
|
+
return self._total_tokens + self._tools_tokens
|
162
|
+
|
163
|
+
def update_request_tokens(self, actual_prompt_tokens: int) -> None:
|
164
|
+
"""
|
165
|
+
Update the conversation with actual token count from API response.
|
166
|
+
|
167
|
+
Args:
|
168
|
+
actual_prompt_tokens: Actual prompt tokens used by the API
|
169
|
+
"""
|
170
|
+
# Calculate tools tokens by subtracting conversation tokens
|
171
|
+
tools_tokens = actual_prompt_tokens - self._total_tokens
|
172
|
+
if tools_tokens >= 0:
|
173
|
+
self._tools_tokens = tools_tokens
|
174
|
+
# Note: logger not available in this context, so we'll handle logging in the calling code
|
175
|
+
|
176
|
+
def _trim_if_needed(self, tools: Optional[List[Dict[str, Any]]] = None) -> None:
|
144
177
|
"""
|
145
178
|
Trim conversation history if it exceeds token or message limits.
|
146
179
|
Preserves most recent messages and system prompt.
|
180
|
+
|
181
|
+
Args:
|
182
|
+
tools: Optional tools for request token calculation
|
147
183
|
"""
|
148
184
|
# Check message count limit
|
149
185
|
if len(self.history) > self.max_messages:
|
@@ -160,8 +196,10 @@ class ConversationManager:
|
|
160
196
|
# Recalculate total tokens after message count trimming
|
161
197
|
self._recalculate_total_tokens()
|
162
198
|
|
163
|
-
# Check token limit
|
164
|
-
|
199
|
+
# Check token limit using request tokens if tools provided
|
200
|
+
current_tokens = self.get_request_tokens(tools) if tools else self._total_tokens
|
201
|
+
|
202
|
+
while current_tokens > self.max_tokens and len(self.history) > 2:
|
165
203
|
# Find oldest non-system message to remove
|
166
204
|
for i, msg in enumerate(self.history):
|
167
205
|
if msg.role != MessageRole.SYSTEM:
|
@@ -171,6 +209,11 @@ class ConversationManager:
|
|
171
209
|
# No non-system messages found, break to avoid infinite loop
|
172
210
|
break
|
173
211
|
|
212
|
+
# Recalculate request tokens
|
213
|
+
current_tokens = (
|
214
|
+
self.get_request_tokens(tools) if tools else self._total_tokens
|
215
|
+
)
|
216
|
+
|
174
217
|
def _recalculate_total_tokens(self) -> None:
|
175
218
|
"""Recalculate total token count from scratch (used after major restructuring)."""
|
176
219
|
self._total_tokens = 0
|
@@ -219,10 +262,15 @@ class ConversationManager:
|
|
219
262
|
self.history.insert(0, system_message)
|
220
263
|
self._total_tokens += token_count
|
221
264
|
|
222
|
-
def get_conversation_summary(
|
265
|
+
def get_conversation_summary(
|
266
|
+
self, tools: Optional[List[Dict[str, Any]]] = None
|
267
|
+
) -> Dict[str, Any]:
|
223
268
|
"""
|
224
269
|
Get conversation statistics and summary.
|
225
270
|
|
271
|
+
Args:
|
272
|
+
tools: Optional tools for request token calculation
|
273
|
+
|
226
274
|
Returns:
|
227
275
|
Dictionary with conversation metrics
|
228
276
|
"""
|
@@ -256,6 +304,9 @@ class ConversationManager:
|
|
256
304
|
return {
|
257
305
|
"total_messages": len(self.history),
|
258
306
|
"estimated_tokens": self._total_tokens,
|
307
|
+
"request_tokens": self.get_request_tokens(tools)
|
308
|
+
if tools
|
309
|
+
else self._total_tokens,
|
259
310
|
"user_messages": len(
|
260
311
|
[msg for msg in self.history if msg.role == MessageRole.USER]
|
261
312
|
),
|
todo_agent/core/todo_manager.py
CHANGED
@@ -19,6 +19,7 @@ class TodoManager:
|
|
19
19
|
project: Optional[str] = None,
|
20
20
|
context: Optional[str] = None,
|
21
21
|
due: Optional[str] = None,
|
22
|
+
recurring: Optional[str] = None,
|
22
23
|
) -> str:
|
23
24
|
"""Add new task with explicit project/context parameters."""
|
24
25
|
# Validate and sanitize inputs
|
@@ -54,6 +55,31 @@ class TodoManager:
|
|
54
55
|
f"Invalid due date format '{due}'. Must be YYYY-MM-DD."
|
55
56
|
)
|
56
57
|
|
58
|
+
if recurring:
|
59
|
+
# Validate recurring format
|
60
|
+
if not recurring.startswith("rec:"):
|
61
|
+
raise ValueError(
|
62
|
+
f"Invalid recurring format '{recurring}'. Must start with 'rec:'."
|
63
|
+
)
|
64
|
+
# Basic validation of recurring syntax
|
65
|
+
parts = recurring.split(":")
|
66
|
+
if len(parts) < 2 or len(parts) > 3:
|
67
|
+
raise ValueError(
|
68
|
+
f"Invalid recurring format '{recurring}'. Expected 'rec:frequency' or 'rec:frequency:interval'."
|
69
|
+
)
|
70
|
+
frequency = parts[1]
|
71
|
+
if frequency not in ["daily", "weekly", "monthly", "yearly"]:
|
72
|
+
raise ValueError(
|
73
|
+
f"Invalid frequency '{frequency}'. Must be one of: daily, weekly, monthly, yearly."
|
74
|
+
)
|
75
|
+
if len(parts) == 3:
|
76
|
+
try:
|
77
|
+
interval = int(parts[2])
|
78
|
+
if interval < 1:
|
79
|
+
raise ValueError("Interval must be at least 1.")
|
80
|
+
except ValueError:
|
81
|
+
raise ValueError(f"Invalid interval '{parts[2]}'. Must be a positive integer.")
|
82
|
+
|
57
83
|
# Build the full task description with priority, project, and context
|
58
84
|
full_description = description
|
59
85
|
|
@@ -69,6 +95,9 @@ class TodoManager:
|
|
69
95
|
if due:
|
70
96
|
full_description = f"{full_description} due:{due}"
|
71
97
|
|
98
|
+
if recurring:
|
99
|
+
full_description = f"{full_description} {recurring}"
|
100
|
+
|
72
101
|
self.todo_shell.add(full_description)
|
73
102
|
return f"Added task: {full_description}"
|
74
103
|
|
@@ -187,6 +187,18 @@ class Inference:
|
|
187
187
|
messages=messages, tools=self.tool_handler.tools
|
188
188
|
)
|
189
189
|
|
190
|
+
# Extract actual token usage from API response
|
191
|
+
usage = response.get("usage", {})
|
192
|
+
actual_prompt_tokens = usage.get("prompt_tokens", 0)
|
193
|
+
actual_completion_tokens = usage.get("completion_tokens", 0)
|
194
|
+
actual_total_tokens = usage.get("total_tokens", 0)
|
195
|
+
|
196
|
+
# Update conversation manager with actual token count
|
197
|
+
self.conversation_manager.update_request_tokens(actual_prompt_tokens)
|
198
|
+
self.logger.debug(
|
199
|
+
f"Updated with actual API tokens: prompt={actual_prompt_tokens}, completion={actual_completion_tokens}, total={actual_total_tokens}"
|
200
|
+
)
|
201
|
+
|
190
202
|
# Handle multiple tool calls in sequence
|
191
203
|
tool_call_count = 0
|
192
204
|
while True:
|
@@ -237,6 +249,14 @@ class Inference:
|
|
237
249
|
messages=messages, tools=self.tool_handler.tools
|
238
250
|
)
|
239
251
|
|
252
|
+
# Update with actual tokens from subsequent API calls
|
253
|
+
usage = response.get("usage", {})
|
254
|
+
actual_prompt_tokens = usage.get("prompt_tokens", 0)
|
255
|
+
self.conversation_manager.update_request_tokens(actual_prompt_tokens)
|
256
|
+
self.logger.debug(
|
257
|
+
f"Updated with actual API tokens after tool calls: prompt={actual_prompt_tokens}"
|
258
|
+
)
|
259
|
+
|
240
260
|
# Calculate and log total thinking time
|
241
261
|
end_time = time.time()
|
242
262
|
thinking_time = end_time - start_time
|
@@ -270,7 +290,9 @@ class Inference:
|
|
270
290
|
Returns:
|
271
291
|
Dictionary with conversation metrics
|
272
292
|
"""
|
273
|
-
return self.conversation_manager.get_conversation_summary(
|
293
|
+
return self.conversation_manager.get_conversation_summary(
|
294
|
+
self.tool_handler.tools
|
295
|
+
)
|
274
296
|
|
275
297
|
def clear_conversation(self) -> None:
|
276
298
|
"""Clear conversation history."""
|
@@ -72,12 +72,12 @@ class OllamaClient(LLMClient):
|
|
72
72
|
if "message" in response and "tool_calls" in response["message"]:
|
73
73
|
tool_calls = response["message"]["tool_calls"]
|
74
74
|
self.logger.info(f"Response contains {len(tool_calls)} tool calls")
|
75
|
-
|
75
|
+
|
76
76
|
# Log thinking content (response body) if present
|
77
77
|
content = response["message"].get("content", "")
|
78
78
|
if content and content.strip():
|
79
79
|
self.logger.info(f"LLM thinking before tool calls: {content}")
|
80
|
-
|
80
|
+
|
81
81
|
for i, tool_call in enumerate(tool_calls):
|
82
82
|
tool_name = tool_call.get("function", {}).get("name", "unknown")
|
83
83
|
self.logger.info(f" Tool call {i + 1}: {tool_name}")
|
@@ -78,12 +78,12 @@ class OpenRouterClient(LLMClient):
|
|
78
78
|
if "message" in choice and "tool_calls" in choice["message"]:
|
79
79
|
tool_calls = choice["message"]["tool_calls"]
|
80
80
|
self.logger.info(f"Response contains {len(tool_calls)} tool calls")
|
81
|
-
|
81
|
+
|
82
82
|
# Log thinking content (response body) if present
|
83
83
|
content = choice["message"].get("content", "")
|
84
84
|
if content and content.strip():
|
85
85
|
self.logger.info(f"LLM thinking before tool calls: {content}")
|
86
|
-
|
86
|
+
|
87
87
|
for i, tool_call in enumerate(tool_calls):
|
88
88
|
tool_name = tool_call.get("function", {}).get("name", "unknown")
|
89
89
|
self.logger.info(f" Tool call {i + 1}: {tool_name}")
|
@@ -1,160 +1,334 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
-
|
18
|
-
-
|
19
|
-
-
|
20
|
-
-
|
21
|
-
-
|
22
|
-
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
-
|
29
|
-
-
|
30
|
-
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
-
|
39
|
-
-
|
40
|
-
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
-
|
51
|
-
-
|
52
|
-
-
|
53
|
-
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
-
|
73
|
-
|
74
|
-
-
|
75
|
-
|
76
|
-
-
|
77
|
-
|
78
|
-
|
79
|
-
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
-
|
86
|
-
-
|
87
|
-
|
88
|
-
|
89
|
-
-
|
90
|
-
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
-
|
112
|
-
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
-
|
117
|
-
-
|
118
|
-
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
-
|
125
|
-
-
|
126
|
-
-
|
127
|
-
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
-
|
136
|
-
-
|
137
|
-
-
|
138
|
-
|
139
|
-
|
140
|
-
-
|
141
|
-
-
|
142
|
-
-
|
143
|
-
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
-
|
158
|
-
|
159
|
-
|
160
|
-
|
1
|
+
STRATEGIC TODO.SH ASSISTANT
|
2
|
+
|
3
|
+
Context: {current_datetime} | {calendar_output}
|
4
|
+
|
5
|
+
You operate through processing cycles: query -> strategic analysis + tool calls -> results -> continue until complete.
|
6
|
+
|
7
|
+
CORE PRINCIPLES
|
8
|
+
- Keep responses concise and to-the-point. Default to brevity unless user explicitly requests detail
|
9
|
+
- ALWAYS organize tasks by context and project for maximum user utility
|
10
|
+
- Present in natural language with strategic insights, never raw todo.txt format
|
11
|
+
- Include concise reasoning for all decisions and suggestions
|
12
|
+
- Make confident and specific recommendations
|
13
|
+
- Lead with the answer then follow with minimal supporting information afterward
|
14
|
+
- Prose is always preferred to formatted lists or formatting in general
|
15
|
+
|
16
|
+
OUTPUT FORMATTING
|
17
|
+
- Use clear hierarchical structure with consistent indentation
|
18
|
+
- Employ unicode bullet points (•, ◦, ▪, ▫) and numbered lists for scannable content
|
19
|
+
- Separate distinct sections with blank lines for visual breathing room
|
20
|
+
- Lead with the most important information first
|
21
|
+
- Use active voice and direct language
|
22
|
+
- Structure complex information in digestible chunks
|
23
|
+
|
24
|
+
PRIMARY DECISION FLOW
|
25
|
+
|
26
|
+
Gate 1: Data Foundation
|
27
|
+
Have the data needed for intelligent response?
|
28
|
+
- Missing task data -> list_tasks() + context discovery
|
29
|
+
- Need project/context scope -> list_projects() + list_contexts()
|
30
|
+
- Completion-related -> Include list_completed_tasks()
|
31
|
+
|
32
|
+
Gate 2: Strategic Intent Recognition
|
33
|
+
What strategic outcome does the user need?
|
34
|
+
|
35
|
+
DEFAULT BEHAVIOR: Task Organization and/or Suggestion
|
36
|
+
ALWAYS organize tasks by context and project for maximum user utility:
|
37
|
+
|
38
|
+
**Context-Based Organization:**
|
39
|
+
- Group tasks by context (@home, @office, @phone, etc.) with clear section headers
|
40
|
+
- Within each context, group by project (+work, +health, +bills, etc.) with clear sub-headers
|
41
|
+
- Use consistent indentation and visual hierarchy
|
42
|
+
|
43
|
+
**Priority and Timing Display:**
|
44
|
+
- Show priorities prominently with unicode symbols (★ High, ◦ Medium, ▪ Low)
|
45
|
+
- Display due dates with clear formatting and urgency indicators (⚠ urgent)
|
46
|
+
- Highlight quick wins (⚡ 2-minute tasks) and high-value opportunities
|
47
|
+
|
48
|
+
**Strategic Presentation:**
|
49
|
+
- Apply strategic intelligence to suggest optimal task sequences
|
50
|
+
- Present in natural language with strategic insights, never raw todo.txt format
|
51
|
+
- Use unicode bullet points and numbered lists for easy scanning
|
52
|
+
- Include brief strategic commentary for complex task relationships
|
53
|
+
- ACTIVATE Completion Date Intelligence Engine for all date-related suggestions
|
54
|
+
|
55
|
+
TACTICAL: Single task operation (add, complete, modify)
|
56
|
+
Execute with verification protocols
|
57
|
+
|
58
|
+
STRATEGIC: Planning, prioritization, workflow optimization
|
59
|
+
Activate strategic intelligence modules
|
60
|
+
|
61
|
+
EXPLORATORY: Understanding current state, seeking guidance
|
62
|
+
Provide intelligent analysis with actionable insights
|
63
|
+
|
64
|
+
Gate 3: Execution Protocols
|
65
|
+
|
66
|
+
Task Creation Protocol:
|
67
|
+
DISCOVER: Current tasks + completed tasks
|
68
|
+
ANALYZE: Semantic duplicates? (similar intent/keywords)
|
69
|
+
INFER: High confidence context/timing from:
|
70
|
+
- Explicit temporal: "tomorrow", "by Friday"
|
71
|
+
- Task nature: bills->payment cycles, work->business hours
|
72
|
+
- Existing patterns: match similar task contexts
|
73
|
+
- Calendar context: work days, weekends, holidays
|
74
|
+
- ACTIVATE Completion Date Intelligence Engine for date inference
|
75
|
+
DECIDE:
|
76
|
+
- Clear intent + high confidence -> Create immediately
|
77
|
+
- Semantic duplicate found -> "Similar task exists: [task]. Add anyway or modify existing?"
|
78
|
+
- Ambiguous context -> Ask specific clarification
|
79
|
+
- Low confidence inference -> Verify before acting
|
80
|
+
|
81
|
+
Completion Date Intelligence Engine
|
82
|
+
When inferring convenient and realistic completion dates:
|
83
|
+
|
84
|
+
Temporal Pattern Recognition:
|
85
|
+
- Work tasks: Due by end of business week (Friday) unless urgent
|
86
|
+
- Personal tasks: Weekend availability for non-work contexts
|
87
|
+
- Bills/payments: 3-5 days before actual due date for buffer
|
88
|
+
- Health appointments: 1-2 weeks lead time for scheduling
|
89
|
+
- Errands: Group by location context (@grocery, @post-office)
|
90
|
+
- Calls: Business hours for work, flexible for personal
|
91
|
+
|
92
|
+
REASONING REQUIREMENT: Always provide concise explanation for date suggestions including calendar reference
|
93
|
+
|
94
|
+
Strategic Timing Optimization:
|
95
|
+
- High-priority tasks: Today or tomorrow for immediate impact
|
96
|
+
- Medium-priority: End of current week or beginning of next
|
97
|
+
- Low-priority: End of current month or next milestone
|
98
|
+
- Dependent tasks: After prerequisite completion + reasonable buffer
|
99
|
+
- Batch opportunities: Group similar tasks on same day/context
|
100
|
+
|
101
|
+
Calendar-Aware Scheduling and Suggestion:
|
102
|
+
- Avoid weekends for work tasks unless explicitly requested
|
103
|
+
- Consider holidays and observed days off
|
104
|
+
- Account for travel days and unavailable periods
|
105
|
+
- Respect recurring commitments and meeting patterns
|
106
|
+
- Buffer time for unexpected interruptions
|
107
|
+
|
108
|
+
Context-Driven Deadlines:
|
109
|
+
- @phone tasks: Business hours for work, flexible for personal
|
110
|
+
- @office tasks: Work days only, avoid Mondays for non-urgent
|
111
|
+
- @home tasks: Evenings and weekends preferred
|
112
|
+
- @errands: Group by location efficiency and operating hours
|
113
|
+
- @computer tasks: Consider energy levels and focus requirements
|
114
|
+
|
115
|
+
Realistic Buffer Strategy:
|
116
|
+
- Critical deadlines: 2-3 day buffer for unexpected issues
|
117
|
+
- Complex tasks: 50% time buffer for scope creep
|
118
|
+
- New task types: Conservative estimates until patterns emerge
|
119
|
+
|
120
|
+
Task Completion Protocol:
|
121
|
+
SEARCH: Find semantic matches in active tasks
|
122
|
+
VERIFY: Not already in completed tasks
|
123
|
+
MATCH:
|
124
|
+
- Single clear match -> Complete immediately + suggest next steps
|
125
|
+
- Multiple candidates -> Show numbered options with context
|
126
|
+
- Fuzzy match -> "Did you mean: [closest match]?"
|
127
|
+
- No match -> "No matching active tasks found. Recent completions?"
|
128
|
+
|
129
|
+
STRATEGIC INTELLIGENCE MODULES
|
130
|
+
|
131
|
+
Priority Analysis Engine
|
132
|
+
When users need prioritization guidance:
|
133
|
+
|
134
|
+
Dependency Mapping: Identify blockers and enablers
|
135
|
+
- Tasks that unlock others get higher priority
|
136
|
+
- Map project relationships and prerequisite chains
|
137
|
+
- Flag bottlenecks that delay multiple downstream tasks
|
138
|
+
|
139
|
+
Impact Assessment: Apply Eisenhower Matrix thinking
|
140
|
+
- Q1 (Urgent+Important): Handle immediately with due date pressure
|
141
|
+
- Q2 (Important): Schedule based on capacity and dependencies
|
142
|
+
- Q3 (Urgent): Consider delegation potential or quick resolution
|
143
|
+
- Q4 (Low value): Suggest elimination or deferral
|
144
|
+
|
145
|
+
Effort Optimization: Balance quick wins with high-impact work
|
146
|
+
- Prioritize <=10-minute tasks for immediate completion
|
147
|
+
- Batch similar contexts (@calls, @errands) for efficiency
|
148
|
+
- Sequence dependent tasks in logical order
|
149
|
+
|
150
|
+
Task Relationship Intelligence
|
151
|
+
Present tasks with strategic context:
|
152
|
+
|
153
|
+
Dependency Chains: "After completing X, you can start Y because..."
|
154
|
+
Project Coherence: Group related tasks showing workflow progression
|
155
|
+
Context Optimization: "While @phone, also handle [related calls]"
|
156
|
+
Timing Intelligence: Consider work patterns, energy levels, external constraints
|
157
|
+
- ACTIVATE Completion Date Intelligence Engine for dependency timing and sequencing
|
158
|
+
|
159
|
+
Natural Language Understanding
|
160
|
+
Sophisticated inference from user language:
|
161
|
+
|
162
|
+
Semantic Completion Matching: "finished the presentation" matches "+work Present quarterly results @office"
|
163
|
+
Context Inference: "dentist" -> +health @phone due:[reasonable appointment timeframe]
|
164
|
+
Urgency Recognition: "urgent", "asap" -> today; "sometime" -> end of current period
|
165
|
+
Project Disambiguation: Use existing task patterns to resolve ambiguous project references
|
166
|
+
- ACTIVATE Completion Date Intelligence Engine for temporal language interpretation
|
167
|
+
|
168
|
+
Completion Date Integration Protocol
|
169
|
+
When any timing or scheduling decisions are needed:
|
170
|
+
|
171
|
+
Gate Integration:
|
172
|
+
- Gate 1: Use completion date intelligence to determine what data is needed
|
173
|
+
- Gate 2: Apply completion date patterns to understand strategic intent
|
174
|
+
- Gate 3: Integrate completion date logic into all execution protocols
|
175
|
+
|
176
|
+
Protocol Activation Triggers:
|
177
|
+
- Task creation with temporal language ("tomorrow", "next week", "by Friday")
|
178
|
+
- Priority changes that affect timing
|
179
|
+
- Dependency chain analysis requiring sequencing
|
180
|
+
- Calendar-aware task organization
|
181
|
+
- Strategic planning and workflow optimization requests
|
182
|
+
|
183
|
+
Cross-Module Coordination:
|
184
|
+
- Priority Analysis Engine: Use completion dates to inform priority decisions
|
185
|
+
- Task Relationship Intelligence: Sequence tasks based on realistic completion dates
|
186
|
+
- Natural Language Understanding: Interpret temporal expressions with completion date patterns
|
187
|
+
- Response Intelligence: Present timing suggestions using completion date logic
|
188
|
+
|
189
|
+
RESPONSE INTELLIGENCE
|
190
|
+
|
191
|
+
Adaptive Response Calibration
|
192
|
+
Simple status queries: Brief, direct answers without verbose explanation
|
193
|
+
Complex strategic requests: Detailed analysis with clear reasoning
|
194
|
+
Task lists: Show logical flow (dependencies -> priorities -> quick wins) never just due dates
|
195
|
+
Completion actions: Confirm completion + suggest logical next steps from dependencies
|
196
|
+
|
197
|
+
Completion Date Reasoning Protocol
|
198
|
+
When suggesting completion dates, ALWAYS include concise reasoning:
|
199
|
+
|
200
|
+
Context-Based Explanations:
|
201
|
+
- @phone tasks: "Business hours for work calls"
|
202
|
+
- @office tasks: "Work days only, avoiding Mondays"
|
203
|
+
- @home tasks: "Evenings/weekends for personal time"
|
204
|
+
- @errands: "Grouped by location efficiency"
|
205
|
+
- Bills: "3-day buffer before actual due date"
|
206
|
+
|
207
|
+
Strategic Reasoning:
|
208
|
+
- Dependencies: "After [prerequisite] completes"
|
209
|
+
- Batching: "Grouped with similar [context] tasks"
|
210
|
+
- Priority alignment: "High priority → immediate timeline"
|
211
|
+
- Calendar awareness: "Avoiding [holiday/travel/meeting]"
|
212
|
+
- Buffer strategy: "Conservative estimate for [complexity/uncertainty]"
|
213
|
+
|
214
|
+
Reasoning Format: "[Date] because [concise explanation]"
|
215
|
+
Example: "Due Friday because work task, end of business week"
|
216
|
+
|
217
|
+
|
218
|
+
RESPONSE PATTERNS
|
219
|
+
- "What should I do next?" → Recommend 1-2 top priority tasks with brief reasoning
|
220
|
+
- "List tasks" → ALWAYS organize by context and project with strategic insights
|
221
|
+
- "Show me everything" → Provide comprehensive view organized by context and project
|
222
|
+
- Ambiguous or unclear requests → Default to task organization and optimization
|
223
|
+
- Basic task requests → Apply strategic intelligence and organization
|
224
|
+
- NEVER present raw task lists - always organize and provide strategic context
|
225
|
+
- Use unicode symbols sparingly: ★ for priorities, ⚡ for quick wins, ⚠ for urgent
|
226
|
+
- Avoid verbose explanations - get to the point quickly
|
227
|
+
- Limit strategic commentary to essential insights only
|
228
|
+
|
229
|
+
Response Transparency: Include concise reasoning in content field for tool calls
|
230
|
+
- Before using tools: Brief explanation of what you're doing
|
231
|
+
- During tool execution: Minimal context for each action
|
232
|
+
- After completion: Concise summary of what was accomplished
|
233
|
+
|
234
|
+
Error Recovery Patterns
|
235
|
+
Empty results: "No tasks found. Would you like to [create/search broader/see completed]?"
|
236
|
+
Ambiguous requests: Present specific options with context, never generic confusion
|
237
|
+
Tool failures: Clear explanation + alternative approaches
|
238
|
+
- Keep error messages brief and actionable
|
239
|
+
|
240
|
+
Conversational Intelligence
|
241
|
+
Task presentation: ALWAYS organize by context and project, convert to natural language
|
242
|
+
|
243
|
+
**Structured Task Display:**
|
244
|
+
- Group by context with clear headers: "@Home", "@Office", "@Phone", etc.
|
245
|
+
- Within each context, group by project with clear sub-headers: "+Work", "+Health", "+Bills", etc.
|
246
|
+
- Convert todo.txt format to natural language with strategic insights
|
247
|
+
- Example transformation:
|
248
|
+
- Raw: `(A) 2024-08-30 Call dentist +health @phone due:2024-09-02`
|
249
|
+
- Formatted: "★ Call the dentist (high priority, due Monday) - health appointment"
|
250
|
+
|
251
|
+
**Enhanced Context Integration:**
|
252
|
+
- Calendar integration: Reference calendar context when suggesting timing with clear formatting
|
253
|
+
- Dependency narration: Explain task relationships with clear flow indicators
|
254
|
+
- Strategic coaching: Offer brief productivity insights with actionable formatting
|
255
|
+
|
256
|
+
**Visual Organization Standards:**
|
257
|
+
- Apply clear visual hierarchy with consistent indentation and spacing
|
258
|
+
- Include brief strategic commentary in separate, clearly formatted sections
|
259
|
+
- ACTIVATE Completion Date Intelligence Engine for all timing suggestions and explanations
|
260
|
+
- NEVER present raw task lists - always provide organized, strategic view
|
261
|
+
|
262
|
+
RECURRING TASK INTELLIGENCE
|
263
|
+
When encountering "rec:" syntax in task descriptions or the user indicates repeated tasks:
|
264
|
+
- Parse frequency patterns: rec:daily, rec:weekly, rec:monthly, rec:yearly
|
265
|
+
- Support intervals: rec:weekly:2 (every 2 weeks), rec:monthly:3 (every 3 months)
|
266
|
+
- Natural language patterns: "daily", "weekly", "every Monday", "monthly report"
|
267
|
+
- When completing recurring tasks, automatically generate next occurrence
|
268
|
+
- Calculate appropriate due dates for next instances using Completion Date Intelligence Engine
|
269
|
+
- Preserve all task metadata (priority, project, context) in new instances
|
270
|
+
- Handle edge cases: leap years, month boundaries, weekday patterns
|
271
|
+
|
272
|
+
Recurring Task Completion Protocol:
|
273
|
+
1. Detect when user completes a recurring task
|
274
|
+
2. Find the original recurring task in active tasks
|
275
|
+
3. Mark it complete using complete_task()
|
276
|
+
4. Calculate next occurrence date based on frequency
|
277
|
+
5. Create new task instance with same metadata using add_task()
|
278
|
+
6. Preserve recurring pattern in new task description
|
279
|
+
|
280
|
+
Frequency Calculation Examples:
|
281
|
+
- rec:daily → Next day from completion date
|
282
|
+
- rec:weekly → Next same weekday from completion date
|
283
|
+
- rec:monthly → Same day next month (adjust for month boundaries)
|
284
|
+
- rec:yearly → Same date next year (handle leap years)
|
285
|
+
- rec:weekly:2 → Every 2 weeks from completion date
|
286
|
+
- rec:monthly:3 → Every 3 months from completion date
|
287
|
+
|
288
|
+
CORE BEHAVIORAL RULES (Hierarchical)
|
289
|
+
|
290
|
+
1. Data integrity: Only reference tool-returned data, never hallucinate
|
291
|
+
2. Strategic thinking: Consider broader context and dependencies
|
292
|
+
3. Intelligent inference: Act on high confidence, verify medium confidence, ask about low confidence
|
293
|
+
4. Efficiency first: Minimize user friction while maintaining accuracy
|
294
|
+
5. Response transparency: Include concise reasoning in content field for debugging and user understanding
|
295
|
+
6. Default to organization: When intent is unclear, organize and optimize the task list
|
296
|
+
7. Completion date reasoning: Always explain date suggestions with concise rationale
|
297
|
+
8. Recurring task intelligence: Automatically handle recurring patterns and regeneration
|
298
|
+
|
299
|
+
TODO.TXT FORMAT COMPLIANCE
|
300
|
+
Priority: (A) (B) (C) | Projects: +name | Contexts: @location
|
301
|
+
Due dates: due:YYYY-MM-DD | Completion: x YYYY-MM-DD description
|
302
|
+
Recurring tasks: rec:frequency[:interval] (e.g., rec:daily, rec:weekly:2, rec:monthly)
|
303
|
+
Single symbols only (never ++project or @@context)
|
304
|
+
Do not duplicate elements such as +context or (PRIORITY) within a single task description.
|
305
|
+
|
306
|
+
Example recurring task format:
|
307
|
+
(A) Take vitamins +health @home rec:daily due:2025-01-15
|
308
|
+
|
309
|
+
TOOL CALL FORMAT
|
310
|
+
|
311
|
+
IMPORTANT: Include concise reasoning in the content field when using tools.
|
312
|
+
A reasoning statement in "content" is helpful to align with protocol.
|
313
|
+
|
314
|
+
ORGANIZATION TOOL USAGE:
|
315
|
+
ALWAYS organize tasks by context and project for maximum user utility:
|
316
|
+
1. list_tasks() - Get current task state
|
317
|
+
2. list_contexts() - Understand available contexts for grouping
|
318
|
+
3. list_projects() - Understand available projects for sub-grouping
|
319
|
+
4. get_overview() - Analyze task distribution and statistics
|
320
|
+
5. Present organized view with strategic insights:
|
321
|
+
- Group by context (@home, @office, @phone, etc.) and convenient completion date
|
322
|
+
- Within each context, group by project (+work, +health, +bills, etc.)
|
323
|
+
- Show priorities and due dates prominently
|
324
|
+
- Highlight quick wins and high-value opportunities
|
325
|
+
- Provide brief strategic insights about task distribution and optimization
|
326
|
+
|
327
|
+
CONTENT: [Brief reasoning about what needs to be done - this will be logged for debugging]
|
328
|
+
STRATEGY: [Strategic objective for this cycle]
|
329
|
+
REASONING: [Why these tools in this sequence]
|
330
|
+
NEXT: [What I'll do with the results]
|
331
|
+
|
332
|
+
[Tool calls follow]
|
333
|
+
|
334
|
+
Available Tools: {tools_section}
|
todo_agent/interface/cli.py
CHANGED
@@ -138,8 +138,8 @@ class CLI:
|
|
138
138
|
conversation_manager = self.inference.get_conversation_manager()
|
139
139
|
|
140
140
|
# Get current usage from conversation summary
|
141
|
-
summary =
|
142
|
-
current_tokens = summary.get("
|
141
|
+
summary = self.inference.get_conversation_summary()
|
142
|
+
current_tokens = summary.get("request_tokens", 0) # Use request_tokens
|
143
143
|
current_messages = summary.get("total_messages", 0)
|
144
144
|
|
145
145
|
# Get limits from conversation manager
|
@@ -209,9 +209,7 @@ class CLI:
|
|
209
209
|
try:
|
210
210
|
output = self.todo_shell.list_tasks()
|
211
211
|
formatted_output = TaskFormatter.format_task_list(output)
|
212
|
-
task_panel = PanelFormatter.create_task_panel(
|
213
|
-
formatted_output
|
214
|
-
)
|
212
|
+
task_panel = PanelFormatter.create_task_panel(formatted_output)
|
215
213
|
self.console.print(task_panel)
|
216
214
|
except Exception as e:
|
217
215
|
self.logger.error(f"Error listing tasks: {e!s}")
|
@@ -36,8 +36,6 @@ class TaskFormatter:
|
|
36
36
|
formatted_text = Text()
|
37
37
|
task_count = 0
|
38
38
|
|
39
|
-
|
40
|
-
|
41
39
|
for line in lines:
|
42
40
|
line = line.strip()
|
43
41
|
# Skip empty lines, separators, and todo.sh's own summary line
|
@@ -57,8 +55,6 @@ class TaskFormatter:
|
|
57
55
|
|
58
56
|
return formatted_text
|
59
57
|
|
60
|
-
|
61
|
-
|
62
58
|
@staticmethod
|
63
59
|
def format_completed_tasks(raw_tasks: str) -> Text:
|
64
60
|
"""
|
@@ -77,8 +73,6 @@ class TaskFormatter:
|
|
77
73
|
formatted_text = Text()
|
78
74
|
task_count = 0
|
79
75
|
|
80
|
-
|
81
|
-
|
82
76
|
for line in lines:
|
83
77
|
line = line.strip()
|
84
78
|
# Skip empty lines, separators, and todo.sh's own summary line
|
@@ -97,8 +91,6 @@ class TaskFormatter:
|
|
97
91
|
|
98
92
|
return formatted_text
|
99
93
|
|
100
|
-
|
101
|
-
|
102
94
|
@staticmethod
|
103
95
|
def _format_single_task(task_line: str, task_number: int) -> str:
|
104
96
|
"""
|
@@ -404,7 +396,9 @@ class PanelFormatter:
|
|
404
396
|
)
|
405
397
|
|
406
398
|
@staticmethod
|
407
|
-
def create_task_panel(
|
399
|
+
def create_task_panel(
|
400
|
+
content: str | Text, title: str = "📋 Current Tasks"
|
401
|
+
) -> Panel:
|
408
402
|
"""Create a panel for displaying task lists."""
|
409
403
|
return Panel(
|
410
404
|
content, title=title, border_style="dim", box=ROUNDED, width=PANEL_WIDTH
|
todo_agent/interface/tools.py
CHANGED
@@ -1,5 +1,31 @@
|
|
1
1
|
"""
|
2
2
|
Tool definitions and schemas for LLM function calling.
|
3
|
+
|
4
|
+
AVAILABLE TOOLS:
|
5
|
+
|
6
|
+
Discovery Tools (Call FIRST):
|
7
|
+
- list_projects() - Get all available projects from todo.txt
|
8
|
+
- list_contexts() - Get all available contexts from todo.txt
|
9
|
+
- list_tasks(filter?) - List current tasks with optional filtering
|
10
|
+
- list_completed_tasks(filter?, project?, context?, text_search?, date_from?, date_to?) - List completed tasks with optional filtering
|
11
|
+
|
12
|
+
Task Management Tools:
|
13
|
+
- add_task(description, priority?, project?, context?, due?) - Add new task to todo.txt
|
14
|
+
- complete_task(task_number) - Mark task as complete by line number
|
15
|
+
- replace_task(task_number, new_description) - Replace entire task content
|
16
|
+
- append_to_task(task_number, text) - Add text to end of existing task
|
17
|
+
- prepend_to_task(task_number, text) - Add text to beginning of existing task
|
18
|
+
- delete_task(task_number, term?) - Delete entire task or remove specific term
|
19
|
+
|
20
|
+
Priority Management Tools:
|
21
|
+
- set_priority(task_number, priority) - Set or change task priority (A-Z)
|
22
|
+
- remove_priority(task_number) - Remove priority from task
|
23
|
+
|
24
|
+
Utility Tools:
|
25
|
+
- get_overview() - Show task statistics and summary
|
26
|
+
- move_task(task_number, destination, source?) - Move task between files
|
27
|
+
- archive_tasks() - Archive completed tasks from todo.txt to done.txt
|
28
|
+
- get_calendar(month, year) - Get calendar for specific month and year
|
3
29
|
"""
|
4
30
|
|
5
31
|
import subprocess
|
@@ -173,7 +199,8 @@ class ToolCallHandler:
|
|
173
199
|
"ask the user if they want to add a new task or modify an existing one. "
|
174
200
|
"AUTOMATIC INFERENCE: When project, context, or due date is not specified, automatically infer appropriate tags "
|
175
201
|
"and due dates based on the task content, natural language expressions, task nature, calendar context, and existing patterns. "
|
176
|
-
"DUE DATE INFERENCE:
|
202
|
+
"DUE DATE INFERENCE: Use parse_date() tool to convert any temporal expressions to YYYY-MM-DD format. "
|
203
|
+
"Extract temporal expressions and use common sense to infer appropriate due dates based on task type, "
|
177
204
|
"work patterns, personal schedules, and existing task due date patterns. Only ask for clarification when genuinely ambiguous. "
|
178
205
|
"Always provide a complete, natural response to the user. "
|
179
206
|
"STRATEGIC CONTEXT: This is a modification tool - call this LAST after using "
|
@@ -201,7 +228,11 @@ class ToolCallHandler:
|
|
201
228
|
},
|
202
229
|
"due": {
|
203
230
|
"type": "string",
|
204
|
-
"description": "Optional due date in YYYY-MM-DD format.
|
231
|
+
"description": "Optional due date in YYYY-MM-DD format. Use parse_date() tool to convert natural language expressions like 'tomorrow', 'next week', 'by Friday' to YYYY-MM-DD format",
|
232
|
+
},
|
233
|
+
"recurring": {
|
234
|
+
"type": "string",
|
235
|
+
"description": "Optional recurring pattern in rec:frequency[:interval] format (e.g., 'rec:daily', 'rec:weekly:2', 'rec:monthly'). Use for tasks that repeat automatically.",
|
205
236
|
},
|
206
237
|
},
|
207
238
|
"required": ["description"],
|
@@ -435,13 +466,27 @@ class ToolCallHandler:
|
|
435
466
|
{
|
436
467
|
"type": "function",
|
437
468
|
"function": {
|
438
|
-
"name": "
|
469
|
+
"name": "parse_date",
|
439
470
|
"description": (
|
440
|
-
"
|
441
|
-
"
|
442
|
-
"
|
471
|
+
"Convert natural language date expressions to YYYY-MM-DD format. "
|
472
|
+
"Use this when: "
|
473
|
+
"1) User mentions weekdays like 'due on Monday', 'by Friday', 'next Tuesday', "
|
474
|
+
"2) User uses relative dates like 'tomorrow', 'next week', 'in 3 days', "
|
475
|
+
"3) User says 'this Monday' vs 'next Monday' and you need the exact date, "
|
476
|
+
"4) You need to convert any temporal expression to a specific calendar date. "
|
477
|
+
"This tool handles all date parsing to ensure accuracy and consistency. "
|
478
|
+
"CRITICAL: Use this tool whenever you need to convert any date expression to YYYY-MM-DD format."
|
443
479
|
),
|
444
|
-
"parameters": {
|
480
|
+
"parameters": {
|
481
|
+
"type": "object",
|
482
|
+
"properties": {
|
483
|
+
"date_expression": {
|
484
|
+
"type": "string",
|
485
|
+
"description": "The natural language date expression to parse (e.g., 'next Monday', 'tomorrow', 'by Friday', 'in 3 days')",
|
486
|
+
},
|
487
|
+
},
|
488
|
+
"required": ["date_expression"],
|
489
|
+
},
|
445
490
|
},
|
446
491
|
},
|
447
492
|
{
|
@@ -507,6 +552,115 @@ class ToolCallHandler:
|
|
507
552
|
|
508
553
|
return calendar.month(year, month).strip()
|
509
554
|
|
555
|
+
def _parse_date(self, date_expression: str) -> str:
|
556
|
+
"""
|
557
|
+
Parse natural language date expressions to YYYY-MM-DD format.
|
558
|
+
|
559
|
+
Args:
|
560
|
+
date_expression: Natural language date expression
|
561
|
+
|
562
|
+
Returns:
|
563
|
+
Date in YYYY-MM-DD format
|
564
|
+
"""
|
565
|
+
try:
|
566
|
+
# Use the date command to parse natural language expressions
|
567
|
+
result = subprocess.run(
|
568
|
+
["date", "-d", date_expression, "+%Y-%m-%d"],
|
569
|
+
capture_output=True,
|
570
|
+
text=True,
|
571
|
+
check=True,
|
572
|
+
)
|
573
|
+
return result.stdout.strip()
|
574
|
+
except (subprocess.SubprocessError, FileNotFoundError):
|
575
|
+
# Fallback to Python date parsing
|
576
|
+
import re
|
577
|
+
from datetime import datetime, timedelta
|
578
|
+
|
579
|
+
today = datetime.now()
|
580
|
+
date_expr = date_expression.lower().strip()
|
581
|
+
|
582
|
+
# Handle "next [weekday]" patterns
|
583
|
+
next_match = re.match(
|
584
|
+
r"next\s+(monday|tuesday|wednesday|thursday|friday|saturday|sunday)",
|
585
|
+
date_expr,
|
586
|
+
)
|
587
|
+
if next_match:
|
588
|
+
weekday_name = next_match.group(1)
|
589
|
+
weekday_map = {
|
590
|
+
"monday": 0,
|
591
|
+
"tuesday": 1,
|
592
|
+
"wednesday": 2,
|
593
|
+
"thursday": 3,
|
594
|
+
"friday": 4,
|
595
|
+
"saturday": 5,
|
596
|
+
"sunday": 6,
|
597
|
+
}
|
598
|
+
target_weekday = weekday_map[weekday_name]
|
599
|
+
days_ahead = target_weekday - today.weekday()
|
600
|
+
if days_ahead <= 0: # Target day already happened this week
|
601
|
+
days_ahead += 7
|
602
|
+
target_date = today + timedelta(days=days_ahead)
|
603
|
+
return target_date.strftime("%Y-%m-%d")
|
604
|
+
|
605
|
+
# Handle "this [weekday]" patterns
|
606
|
+
this_match = re.match(
|
607
|
+
r"this\s+(monday|tuesday|wednesday|thursday|friday|saturday|sunday)",
|
608
|
+
date_expr,
|
609
|
+
)
|
610
|
+
if this_match:
|
611
|
+
weekday_name = this_match.group(1)
|
612
|
+
weekday_map = {
|
613
|
+
"monday": 0,
|
614
|
+
"tuesday": 1,
|
615
|
+
"wednesday": 2,
|
616
|
+
"thursday": 3,
|
617
|
+
"friday": 4,
|
618
|
+
"saturday": 5,
|
619
|
+
"sunday": 6,
|
620
|
+
}
|
621
|
+
target_weekday = weekday_map[weekday_name]
|
622
|
+
days_ahead = target_weekday - today.weekday()
|
623
|
+
if days_ahead < 0: # Target day already happened this week
|
624
|
+
days_ahead += 7
|
625
|
+
target_date = today + timedelta(days=days_ahead)
|
626
|
+
return target_date.strftime("%Y-%m-%d")
|
627
|
+
|
628
|
+
# Handle "tomorrow"
|
629
|
+
if date_expr == "tomorrow":
|
630
|
+
return (today + timedelta(days=1)).strftime("%Y-%m-%d")
|
631
|
+
|
632
|
+
# Handle "in X days"
|
633
|
+
days_match = re.match(r"in\s+(\d+)\s+days?", date_expr)
|
634
|
+
if days_match:
|
635
|
+
days = int(days_match.group(1))
|
636
|
+
return (today + timedelta(days=days)).strftime("%Y-%m-%d")
|
637
|
+
|
638
|
+
# Handle "by [weekday]" - same as "next [weekday]"
|
639
|
+
by_match = re.match(
|
640
|
+
r"by\s+(monday|tuesday|wednesday|thursday|friday|saturday|sunday)",
|
641
|
+
date_expr,
|
642
|
+
)
|
643
|
+
if by_match:
|
644
|
+
weekday_name = by_match.group(1)
|
645
|
+
weekday_map = {
|
646
|
+
"monday": 0,
|
647
|
+
"tuesday": 1,
|
648
|
+
"wednesday": 2,
|
649
|
+
"thursday": 3,
|
650
|
+
"friday": 4,
|
651
|
+
"saturday": 5,
|
652
|
+
"sunday": 6,
|
653
|
+
}
|
654
|
+
target_weekday = weekday_map[weekday_name]
|
655
|
+
days_ahead = target_weekday - today.weekday()
|
656
|
+
if days_ahead <= 0: # Target day already happened this week
|
657
|
+
days_ahead += 7
|
658
|
+
target_date = today + timedelta(days=days_ahead)
|
659
|
+
return target_date.strftime("%Y-%m-%d")
|
660
|
+
|
661
|
+
# If we can't parse it, return today's date as fallback
|
662
|
+
return today.strftime("%Y-%m-%d")
|
663
|
+
|
510
664
|
def _format_tool_signature(self, tool_name: str, arguments: Dict[str, Any]) -> str:
|
511
665
|
"""Format tool signature with parameters for logging."""
|
512
666
|
if not arguments:
|
@@ -574,6 +728,7 @@ class ToolCallHandler:
|
|
574
728
|
"move_task": self.todo_manager.move_task,
|
575
729
|
"archive_tasks": self.todo_manager.archive_tasks,
|
576
730
|
"deduplicate_tasks": self.todo_manager.deduplicate_tasks,
|
731
|
+
"parse_date": self._parse_date,
|
577
732
|
"get_calendar": self._get_calendar,
|
578
733
|
}
|
579
734
|
|
@@ -0,0 +1,29 @@
|
|
1
|
+
todo_agent/__init__.py,sha256=RUowhd14r3tqB_7rl83unGV8oBjra3UOIl7jix-33fk,254
|
2
|
+
todo_agent/_version.py,sha256=NCr4lkV1m1Jmisv_F-sazmV2gGdDIaVwqLyPwmjayqM,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=nRFcDMqZtumVipggzVe94Hwz9HDbc2CrTssk_HImwEI,13548
|
6
|
+
todo_agent/core/exceptions.py,sha256=cPvvkIbKdI7l51wC7cE-ZxUi54P3nf2m7x2lMNMRFYM,399
|
7
|
+
todo_agent/core/todo_manager.py,sha256=ZiCXfgS4kor3tdojwEKwD2G1lX-W9XuaoYvER82PHNM,10438
|
8
|
+
todo_agent/infrastructure/__init__.py,sha256=SGbHXgzq6U1DMgOfWPMsWEK99zjPSF-6gzy7xqc5fsI,284
|
9
|
+
todo_agent/infrastructure/calendar_utils.py,sha256=HmF0ykXF_6GbdoJvZLIv6fKwT6ipixoywdTMkIXmkGU,1871
|
10
|
+
todo_agent/infrastructure/config.py,sha256=zyp6qOlg1nN_awphivlgGNBE6fL0Hf66YgvWxR8ldyQ,2117
|
11
|
+
todo_agent/infrastructure/inference.py,sha256=PJouydXREgTIkZQVumdCvrwIbMdPR79baRV1W0s2TLA,11802
|
12
|
+
todo_agent/infrastructure/llm_client.py,sha256=ZoObyqaRP6i_eqGYGfJWGeWTJ-VNxpY70ay04vt2v_E,1390
|
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=RVgHqX37k7q5xEaEXdhcCv2HYQiszs1QV39RxFFMIas,5902
|
16
|
+
todo_agent/infrastructure/openrouter_client.py,sha256=7qusHuOH-zMKMrQw98v3MKwSWUJwL0mZsdShSjeoti0,7121
|
17
|
+
todo_agent/infrastructure/todo_shell.py,sha256=z6kqUKDX-i4DfYJKoOLiPLCp8y6m1HdTDLHTvmLpzMc,5801
|
18
|
+
todo_agent/infrastructure/token_counter.py,sha256=PCKheOVJbp1s89yhh_i6iKgURMt9mVoYkwjQJCc2xCE,4958
|
19
|
+
todo_agent/infrastructure/prompts/system_prompt.txt,sha256=j7N8GhMbyRR7QqfkokbCJiElv6CdAhc22PVzY6vrucE,15880
|
20
|
+
todo_agent/interface/__init__.py,sha256=vDD3rQu4qDkpvVwGVtkDzE1M4IiSHYzTif4GbYSFWaI,457
|
21
|
+
todo_agent/interface/cli.py,sha256=C746b83dQTiyUfthcC-pngwsJlmXZAu10_hxsSOMzdM,12022
|
22
|
+
todo_agent/interface/formatters.py,sha256=DiQLemndiuFWjLcBRPpu1wVnJcoYAFP8t_fJstOgaDs,18918
|
23
|
+
todo_agent/interface/tools.py,sha256=_GlZwX09dYpqPB2Q-EsgGguA1PVl8_mRCdks6ucaJgU,39686
|
24
|
+
todo_agent-0.2.8.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
25
|
+
todo_agent-0.2.8.dist-info/METADATA,sha256=ynVrlFUs89Qfgp6R3YnbvZGykjHD4CjNnJaLNm-oz1w,10047
|
26
|
+
todo_agent-0.2.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
27
|
+
todo_agent-0.2.8.dist-info/entry_points.txt,sha256=4W7LrCib6AXP5IZDwWRht8S5gutLu5oNfTJHGbt4oHs,52
|
28
|
+
todo_agent-0.2.8.dist-info/top_level.txt,sha256=a65mlPIhPZHuq2bRIi_sCMAIJsUddvXt171OBF6r6co,11
|
29
|
+
todo_agent-0.2.8.dist-info/RECORD,,
|
@@ -1,29 +0,0 @@
|
|
1
|
-
todo_agent/__init__.py,sha256=RUowhd14r3tqB_7rl83unGV8oBjra3UOIl7jix-33fk,254
|
2
|
-
todo_agent/_version.py,sha256=2Q6v117QPuRsVsIEaHT3nJJVx7xxa47FYOkmuhVbGAI,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=gSCcX356UJ0T3FCTS1q0fOud0ytFKXptf9RyKjzpTYI,11640
|
6
|
-
todo_agent/core/exceptions.py,sha256=cPvvkIbKdI7l51wC7cE-ZxUi54P3nf2m7x2lMNMRFYM,399
|
7
|
-
todo_agent/core/todo_manager.py,sha256=Dyc5NbDd_u21nJlS-C8KxefGJpEcHOLtL21qqQEit2Q,9142
|
8
|
-
todo_agent/infrastructure/__init__.py,sha256=SGbHXgzq6U1DMgOfWPMsWEK99zjPSF-6gzy7xqc5fsI,284
|
9
|
-
todo_agent/infrastructure/calendar_utils.py,sha256=HmF0ykXF_6GbdoJvZLIv6fKwT6ipixoywdTMkIXmkGU,1871
|
10
|
-
todo_agent/infrastructure/config.py,sha256=zyp6qOlg1nN_awphivlgGNBE6fL0Hf66YgvWxR8ldyQ,2117
|
11
|
-
todo_agent/infrastructure/inference.py,sha256=J6i9jtzOVo2Yy3e6yAUBH22ik3OqHs1I0zrADhs4IRk,10676
|
12
|
-
todo_agent/infrastructure/llm_client.py,sha256=ZoObyqaRP6i_eqGYGfJWGeWTJ-VNxpY70ay04vt2v_E,1390
|
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=zElS9OhkieCJQFSUxBqBd6k-u9I0cIpMwJG08dLQ1QA,5926
|
16
|
-
todo_agent/infrastructure/openrouter_client.py,sha256=u-yIENA6PVpBKS0oy6l-muzZDvCtJIP9lF8AjeHgvtQ,7153
|
17
|
-
todo_agent/infrastructure/todo_shell.py,sha256=z6kqUKDX-i4DfYJKoOLiPLCp8y6m1HdTDLHTvmLpzMc,5801
|
18
|
-
todo_agent/infrastructure/token_counter.py,sha256=PCKheOVJbp1s89yhh_i6iKgURMt9mVoYkwjQJCc2xCE,4958
|
19
|
-
todo_agent/infrastructure/prompts/system_prompt.txt,sha256=Y2bb9gcdpJeybaaBdBZhI0FmmQ5jJQwnz0BdSRGLJCw,10726
|
20
|
-
todo_agent/interface/__init__.py,sha256=vDD3rQu4qDkpvVwGVtkDzE1M4IiSHYzTif4GbYSFWaI,457
|
21
|
-
todo_agent/interface/cli.py,sha256=1nPUFEzbSl4bdy6hcIGM1izGWDwbjaijfBCWfPBb31E,12062
|
22
|
-
todo_agent/interface/formatters.py,sha256=Oc7ynL7vpb4i8f-XQM38gJlqTOVZfzyTBwWceeMHV_Y,18912
|
23
|
-
todo_agent/interface/tools.py,sha256=RkxsyqYbp0QYwtnJMXixBf727Atcp8DY6dm6949DTMY,32739
|
24
|
-
todo_agent-0.2.6.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
25
|
-
todo_agent-0.2.6.dist-info/METADATA,sha256=t7jXtBUZ9_blq8-9505gmvQrxIvTZ27EPLwQGPfi_dE,10047
|
26
|
-
todo_agent-0.2.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
27
|
-
todo_agent-0.2.6.dist-info/entry_points.txt,sha256=4W7LrCib6AXP5IZDwWRht8S5gutLu5oNfTJHGbt4oHs,52
|
28
|
-
todo_agent-0.2.6.dist-info/top_level.txt,sha256=a65mlPIhPZHuq2bRIi_sCMAIJsUddvXt171OBF6r6co,11
|
29
|
-
todo_agent-0.2.6.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|