raggiecode 0.2.1__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.
- Agent/__init__.py +0 -0
- Agent/agent.py +891 -0
- Agent/chat_history_db.py +1500 -0
- Agent/command.py +49 -0
- Agent/config.py +46 -0
- Agent/effort_levels.py +33 -0
- Agent/git_manager.py +727 -0
- Agent/tools.py +35 -0
- Commands/__init__.py +18 -0
- Commands/effort.py +42 -0
- Commands/global_todo.py +23 -0
- Commands/help.py +22 -0
- Commands/reasoning.py +24 -0
- Commands/redo.py +11 -0
- Commands/reindex.py +27 -0
- Commands/shell.py +28 -0
- Commands/stream.py +24 -0
- Commands/undo.py +13 -0
- Commands/unlimited_effort.py +8 -0
- Commands/window_size.py +29 -0
- RAG/__init__.py +0 -0
- RAG/document.py +119 -0
- RAG/find.py +408 -0
- RAG/graph.py +231 -0
- Tools/GetFileCodeStructure.py +43 -0
- Tools/GetSymbolSourceCode.py +27 -0
- Tools/__init__.py +39 -0
- Tools/ask_user.py +102 -0
- Tools/dispatch_subagent.py +215 -0
- Tools/document.py +35 -0
- Tools/edit_symbol.py +250 -0
- Tools/fuzzy_search.py +119 -0
- Tools/list_dir.py +51 -0
- Tools/read.py +49 -0
- Tools/read_image.py +75 -0
- Tools/remove.py +75 -0
- Tools/replace.py +305 -0
- Tools/search.py +41 -0
- Tools/shell.py +149 -0
- Tools/shell_kill.py +87 -0
- Tools/temp_background_service.py +113 -0
- Tools/todo_list.py +481 -0
- Tools/utils.py +116 -0
- Tools/view_changes.py +179 -0
- Tools/walk_call_tree.py +30 -0
- Tools/web_fetch.py +175 -0
- Tools/web_search.py +69 -0
- Tools/write.py +48 -0
- cli.py +111 -0
- config/__init__.py +0 -0
- config/coder_system_prompt.md +119 -0
- config/roles.json +43 -0
- config/tools.json +709 -0
- indexing/__init__.py +0 -0
- indexing/cli.py +128 -0
- indexing/code_index_sdk.py +832 -0
- indexing/code_indexer.py +1763 -0
- indexing/db_schema.py +396 -0
- indexing/export_to_json.py +346 -0
- indexing/extractors.py +189 -0
- indexing/file_utils.py +97 -0
- indexing/frontend/__init__.py +0 -0
- indexing/frontend/css_extractor.py +195 -0
- indexing/frontend/css_parser.py +387 -0
- indexing/frontend/css_selector_utils.py +226 -0
- indexing/frontend/edit_safety.py +573 -0
- indexing/frontend/graph.py +838 -0
- indexing/frontend/html_extractor.py +496 -0
- indexing/frontend/html_parser.py +314 -0
- indexing/frontend/jsx_extractor.py +1204 -0
- indexing/frontend/location_lookup.py +247 -0
- indexing/frontend/resolver.py +485 -0
- indexing/frontend/runtime_resolver.py +862 -0
- indexing/frontend/semantic_output.py +705 -0
- indexing/frontend/source_location.py +69 -0
- indexing/frontend_config.py +72 -0
- indexing/frontend_models.py +347 -0
- indexing/language_config.py +360 -0
- indexing/models.py +284 -0
- indexing/node_utils.py +1112 -0
- indexing/parse_worker.py +1082 -0
- indexing/queries.py +1542 -0
- indexing/sdk_examples.py +426 -0
- interactive.py +248 -0
- raggie.py +673 -0
- raggiecode-0.2.1.dist-info/METADATA +944 -0
- raggiecode-0.2.1.dist-info/RECORD +93 -0
- raggiecode-0.2.1.dist-info/WHEEL +5 -0
- raggiecode-0.2.1.dist-info/entry_points.txt +2 -0
- raggiecode-0.2.1.dist-info/top_level.txt +10 -0
- skills/__init__.py +3 -0
- skills/manager.py +114 -0
- skills/tool.py +121 -0
Tools/todo_list.py
ADDED
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
from .utils import BLUE, RESET, GREEN, YELLOW, RED, GRAY
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def handle_create_todo_list(arguments, toolcall_id, parent_session_id=None):
|
|
5
|
+
"""Create a new todo list for the current session."""
|
|
6
|
+
from Agent.chat_history_db import create_todo_list, get_session_effort, get_session_depth, resolve_todo_session_id
|
|
7
|
+
from Agent.effort_levels import is_depth_allowed, effort_name, effort_max_depth
|
|
8
|
+
|
|
9
|
+
if parent_session_id is None:
|
|
10
|
+
return {
|
|
11
|
+
"role": "tool",
|
|
12
|
+
"tool_call_id": toolcall_id,
|
|
13
|
+
"content": "Error: parent_session_id is required to create a todo list",
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
# When globalTodo is enabled, resolve to the root session so all subagents share one todo list
|
|
17
|
+
todo_session_id = resolve_todo_session_id(parent_session_id)
|
|
18
|
+
|
|
19
|
+
effort = get_session_effort(parent_session_id)
|
|
20
|
+
depth = get_session_depth(parent_session_id)
|
|
21
|
+
if effort is not None and not is_depth_allowed(effort, depth):
|
|
22
|
+
max_d = effort_max_depth(effort)
|
|
23
|
+
return {
|
|
24
|
+
"role": "tool",
|
|
25
|
+
"tool_call_id": toolcall_id,
|
|
26
|
+
"content": f"Cannot create todo list: effort level '{effort_name(effort)}' limits todo list depth to {max_d}. Current session depth is {depth}. Handle the task directly without a todo list.",
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
todo_list_id = create_todo_list(todo_session_id)
|
|
31
|
+
print(f"{BLUE}Created todo list {todo_list_id} for session {todo_session_id}{RESET}")
|
|
32
|
+
return {
|
|
33
|
+
"role": "tool",
|
|
34
|
+
"tool_call_id": toolcall_id,
|
|
35
|
+
"content": f"Created todo list with ID: {todo_list_id}",
|
|
36
|
+
}
|
|
37
|
+
except Exception as e:
|
|
38
|
+
return {
|
|
39
|
+
"role": "tool",
|
|
40
|
+
"tool_call_id": toolcall_id,
|
|
41
|
+
"content": f"Failed to create todo list: {str(e)}",
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def handle_add_task(arguments, toolcall_id, parent_session_id=None):
|
|
46
|
+
"""Add a task to a todo list."""
|
|
47
|
+
from Agent.chat_history_db import add_todo_task
|
|
48
|
+
|
|
49
|
+
todo_list_id = arguments.get("todo_list_id")
|
|
50
|
+
goal = arguments.get("goal")
|
|
51
|
+
requirements = arguments.get("requirements")
|
|
52
|
+
notes = arguments.get("notes")
|
|
53
|
+
context = arguments.get("context")
|
|
54
|
+
insert_after = arguments.get("insert_after")
|
|
55
|
+
|
|
56
|
+
if not todo_list_id or not goal:
|
|
57
|
+
return {
|
|
58
|
+
"role": "tool",
|
|
59
|
+
"tool_call_id": toolcall_id,
|
|
60
|
+
"content": "Error: 'todo_list_id' and 'goal' are required parameters",
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
task_id = add_todo_task(todo_list_id, goal, requirements, notes,
|
|
65
|
+
context=context, insert_after=insert_after)
|
|
66
|
+
print(f"{BLUE}Added task {task_id} to todo list {todo_list_id}: {goal[:50]}...{RESET}")
|
|
67
|
+
return {
|
|
68
|
+
"role": "tool",
|
|
69
|
+
"tool_call_id": toolcall_id,
|
|
70
|
+
"content": f"Added task with ID: {task_id}",
|
|
71
|
+
}
|
|
72
|
+
except Exception as e:
|
|
73
|
+
return {
|
|
74
|
+
"role": "tool",
|
|
75
|
+
"tool_call_id": toolcall_id,
|
|
76
|
+
"content": f"Failed to add task: {str(e)}",
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def handle_get_todo_list(arguments, toolcall_id, parent_session_id=None):
|
|
81
|
+
"""Get and display a todo list with all its tasks."""
|
|
82
|
+
from Agent.chat_history_db import get_todo_list, get_todo_tasks
|
|
83
|
+
|
|
84
|
+
todo_list_id = arguments.get("todo_list_id")
|
|
85
|
+
|
|
86
|
+
if not todo_list_id:
|
|
87
|
+
return {
|
|
88
|
+
"role": "tool",
|
|
89
|
+
"tool_call_id": toolcall_id,
|
|
90
|
+
"content": "Error: 'todo_list_id' is required",
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
todo_list = get_todo_list(todo_list_id)
|
|
95
|
+
if not todo_list:
|
|
96
|
+
return {
|
|
97
|
+
"role": "tool",
|
|
98
|
+
"tool_call_id": toolcall_id,
|
|
99
|
+
"content": f"Todo list {todo_list_id} not found",
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
tasks = get_todo_tasks(todo_list_id)
|
|
103
|
+
|
|
104
|
+
output = f"\n{BLUE}Todo List {todo_list_id} (Status: {todo_list['status']}){RESET}\n"
|
|
105
|
+
output += "=" * 60 + "\n"
|
|
106
|
+
|
|
107
|
+
for task in tasks:
|
|
108
|
+
status_color = GREEN if task['status'] == 'completed' else YELLOW if task['status'] == 'in_progress' else RESET
|
|
109
|
+
output += f"{status_color}[{task['status']}] {YELLOW}{task['order_index'] + 1}.{RESET} {task['goal']}{RESET}\n"
|
|
110
|
+
if task['requirements']:
|
|
111
|
+
output += f" Requirements: {task['requirements']}\n"
|
|
112
|
+
if task['notes']:
|
|
113
|
+
output += f" Notes: {task['notes']}\n"
|
|
114
|
+
if task['context']:
|
|
115
|
+
output += f"{GRAY} Context: {task['context']}\n{RESET}"
|
|
116
|
+
if task.get('cancel_reason'):
|
|
117
|
+
output += f"{YELLOW} Cancel reason: {task['cancel_reason']}{RESET}\n"
|
|
118
|
+
output += "\n"
|
|
119
|
+
|
|
120
|
+
print(output)
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
"role": "tool",
|
|
124
|
+
"tool_call_id": toolcall_id,
|
|
125
|
+
"content": f"Retrieved todo list with {len(tasks)} tasks",
|
|
126
|
+
}
|
|
127
|
+
except Exception as e:
|
|
128
|
+
return {
|
|
129
|
+
"role": "tool",
|
|
130
|
+
"tool_call_id": toolcall_id,
|
|
131
|
+
"content": f"Failed to get todo list: {str(e)}",
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def handle_approve_todo_list(arguments, toolcall_id, parent_session_id=None):
|
|
136
|
+
"""Approve a todo list and mark it as ready for execution."""
|
|
137
|
+
from Agent.chat_history_db import update_todo_list_status
|
|
138
|
+
from prompt_toolkit import prompt
|
|
139
|
+
|
|
140
|
+
todo_list_id = arguments.get("todo_list_id")
|
|
141
|
+
|
|
142
|
+
if not todo_list_id:
|
|
143
|
+
return {
|
|
144
|
+
"role": "tool",
|
|
145
|
+
"tool_call_id": toolcall_id,
|
|
146
|
+
"content": "Error: 'todo_list_id' is required",
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
# Display the todo list first
|
|
151
|
+
from Agent.chat_history_db import get_todo_tasks
|
|
152
|
+
tasks = get_todo_tasks(todo_list_id)
|
|
153
|
+
|
|
154
|
+
print(f"\n{BLUE}Todo List {todo_list_id} - Execution Plan{RESET}")
|
|
155
|
+
print("=" * 60)
|
|
156
|
+
for task in tasks:
|
|
157
|
+
print(f"{YELLOW}{task['order_index'] + 1}.{RESET} {task['goal']}")
|
|
158
|
+
if task['requirements']:
|
|
159
|
+
print(f" Requirements: {task['requirements']}")
|
|
160
|
+
if task['notes']:
|
|
161
|
+
print(f" Notes: {task['notes']}")
|
|
162
|
+
if task['context']:
|
|
163
|
+
print(f"{GRAY} Context: {task['context']}{RESET}")
|
|
164
|
+
print("=" * 60)
|
|
165
|
+
|
|
166
|
+
# Ask for user approval
|
|
167
|
+
user_input = prompt("Do you want me to carry on with this plan? (y/n): ").strip().lower()
|
|
168
|
+
|
|
169
|
+
if user_input == 'y':
|
|
170
|
+
update_todo_list_status(todo_list_id, 'approved')
|
|
171
|
+
print(f"{GREEN}Todo list approved. Starting execution...{RESET}")
|
|
172
|
+
return {
|
|
173
|
+
"role": "tool",
|
|
174
|
+
"tool_call_id": toolcall_id,
|
|
175
|
+
"content": "Todo list approved and ready for execution",
|
|
176
|
+
}
|
|
177
|
+
else:
|
|
178
|
+
# Ask for feedback
|
|
179
|
+
feedback = prompt("Please elaborate on what should be changed: ").strip()
|
|
180
|
+
update_todo_list_status(todo_list_id, 'rejected')
|
|
181
|
+
print(f"{RED}Todo list rejected. Feedback: {feedback}{RESET}")
|
|
182
|
+
return {
|
|
183
|
+
"role": "tool",
|
|
184
|
+
"tool_call_id": toolcall_id,
|
|
185
|
+
"content": f"Todo list rejected. User feedback: {feedback}",
|
|
186
|
+
}
|
|
187
|
+
except Exception as e:
|
|
188
|
+
return {
|
|
189
|
+
"role": "tool",
|
|
190
|
+
"tool_call_id": toolcall_id,
|
|
191
|
+
"content": f"Failed to approve todo list: {str(e)}",
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def handle_execute_next_task(arguments, toolcall_id, parent_session_id=None):
|
|
196
|
+
"""Execute the next pending task in the todo list by dispatching a subagent.
|
|
197
|
+
|
|
198
|
+
If a task is already in_progress (from a crashed previous attempt),
|
|
199
|
+
it is resumed using the original toolcall_id so dispatch_subagent
|
|
200
|
+
can find and resume the existing child session.
|
|
201
|
+
"""
|
|
202
|
+
from Agent.chat_history_db import get_next_pending_task, update_task_status, get_todo_list, get_todo_tasks, delete_todo_list, get_all_session_files_chain, set_task_toolcall_id, get_session_effort, get_session_depth
|
|
203
|
+
from Tools.dispatch_subagent import handle as dispatch_handle
|
|
204
|
+
from Agent.effort_levels import is_depth_allowed, effort_name, effort_max_depth
|
|
205
|
+
|
|
206
|
+
todo_list_id = arguments.get("todo_list_id")
|
|
207
|
+
|
|
208
|
+
if not todo_list_id:
|
|
209
|
+
return {
|
|
210
|
+
"role": "tool",
|
|
211
|
+
"tool_call_id": toolcall_id,
|
|
212
|
+
"content": "Error: 'todo_list_id' is required",
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if parent_session_id is not None:
|
|
216
|
+
effort = get_session_effort(parent_session_id)
|
|
217
|
+
depth = get_session_depth(parent_session_id)
|
|
218
|
+
if effort is not None and not is_depth_allowed(effort, depth):
|
|
219
|
+
max_d = effort_max_depth(effort)
|
|
220
|
+
return {
|
|
221
|
+
"role": "tool",
|
|
222
|
+
"tool_call_id": toolcall_id,
|
|
223
|
+
"content": f"Cannot execute todo list tasks: effort level '{effort_name(effort)}' limits todo list depth to {max_d}. Current session depth is {depth}. Handle remaining tasks directly without dispatching subagents.",
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
try:
|
|
227
|
+
# Check if todo list is approved before executing any task
|
|
228
|
+
todo_list = get_todo_list(todo_list_id)
|
|
229
|
+
if not todo_list:
|
|
230
|
+
return {
|
|
231
|
+
"role": "tool",
|
|
232
|
+
"tool_call_id": toolcall_id,
|
|
233
|
+
"content": f"Error: Todo list {todo_list_id} not found",
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if todo_list['status'] != 'approved':
|
|
237
|
+
return {
|
|
238
|
+
"role": "tool",
|
|
239
|
+
"tool_call_id": toolcall_id,
|
|
240
|
+
"content": f"Error: Cannot execute tasks. Todo list {todo_list_id} has not been approved by the user. Current status: {todo_list['status']}. Please call ApproveTodoList first.",
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
# Get the next task — in_progress tasks are returned first (crash recovery)
|
|
244
|
+
task = get_next_pending_task(todo_list_id)
|
|
245
|
+
|
|
246
|
+
if not task:
|
|
247
|
+
# No more pending tasks — delete the completed todo list
|
|
248
|
+
delete_todo_list(todo_list_id)
|
|
249
|
+
print(f"{GREEN}All tasks completed! Todo list {todo_list_id} is done and has been removed.{RESET}")
|
|
250
|
+
return {
|
|
251
|
+
"role": "tool",
|
|
252
|
+
"tool_call_id": toolcall_id,
|
|
253
|
+
"content": "All tasks completed. Todo list has been removed.",
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
is_resume = task['status'] == 'in_progress'
|
|
257
|
+
|
|
258
|
+
if is_resume:
|
|
259
|
+
# Crash recovery: resume the existing subagent session using the original toolcall_id
|
|
260
|
+
print(f"{BLUE}Resuming task {task['order_index'] + 1}: {task['goal']}{RESET}")
|
|
261
|
+
dispatch_toolcall_id = task.get('toolcall_id') or toolcall_id
|
|
262
|
+
else:
|
|
263
|
+
# New task: mark as in_progress and store the toolcall_id for future recovery
|
|
264
|
+
update_task_status(task['id'], 'in_progress')
|
|
265
|
+
set_task_toolcall_id(task['id'], toolcall_id)
|
|
266
|
+
print(f"{BLUE}Executing task {task['order_index'] + 1}: {task['goal']}{RESET}")
|
|
267
|
+
dispatch_toolcall_id = toolcall_id
|
|
268
|
+
|
|
269
|
+
# Get all tasks to build context of completed/cancelled tasks
|
|
270
|
+
all_tasks = get_todo_tasks(todo_list_id)
|
|
271
|
+
completed_tasks = [t for t in all_tasks if t['status'] == 'completed']
|
|
272
|
+
cancelled_tasks = [t for t in all_tasks if t['status'] == 'cancelled']
|
|
273
|
+
|
|
274
|
+
# Build prompt for subagent
|
|
275
|
+
prompt_parts = [f"Goal: {task['goal']}"]
|
|
276
|
+
if task['requirements']:
|
|
277
|
+
prompt_parts.append(f"Requirements: {task['requirements']}")
|
|
278
|
+
if task['notes']:
|
|
279
|
+
prompt_parts.append(f"Notes: {task['notes']}")
|
|
280
|
+
if task['context']:
|
|
281
|
+
prompt_parts.append(f"Context: {task['context']}")
|
|
282
|
+
|
|
283
|
+
# Add context from previously completed tasks
|
|
284
|
+
if completed_tasks:
|
|
285
|
+
prompt_parts.append("\nPreviously completed tasks in this todo list:")
|
|
286
|
+
for ct in completed_tasks:
|
|
287
|
+
prompt_parts.append(f" - [COMPLETED] {ct['order_index'] + 1}. {ct['goal']}")
|
|
288
|
+
if ct['requirements']:
|
|
289
|
+
prompt_parts.append(f" Requirements: {ct['requirements']}")
|
|
290
|
+
|
|
291
|
+
# Add context from cancelled tasks
|
|
292
|
+
if cancelled_tasks:
|
|
293
|
+
prompt_parts.append("\nCancelled tasks in this todo list:")
|
|
294
|
+
for ct in cancelled_tasks:
|
|
295
|
+
prompt_parts.append(f" - [CANCELLED] {ct['order_index'] + 1}. {ct['goal']}")
|
|
296
|
+
if ct['notes']:
|
|
297
|
+
prompt_parts.append(f" Notes: {ct['notes']}")
|
|
298
|
+
|
|
299
|
+
subagent_prompt = "\n".join(prompt_parts)
|
|
300
|
+
|
|
301
|
+
# Dispatch subagent (use original toolcall_id for resume so the child session is found)
|
|
302
|
+
dispatch_args = {
|
|
303
|
+
"prompt": subagent_prompt
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
result = dispatch_handle(dispatch_args, dispatch_toolcall_id, parent_session_id, skip_depth_check=True)
|
|
307
|
+
|
|
308
|
+
# Mark task as completed if subagent succeeded
|
|
309
|
+
if result.get('role') == 'tool' and not result.get('content', '').startswith('Error'):
|
|
310
|
+
update_task_status(task['id'], 'completed')
|
|
311
|
+
if is_resume:
|
|
312
|
+
print(f"{GREEN}Task {task['order_index'] + 1} resumed and completed{RESET}")
|
|
313
|
+
else:
|
|
314
|
+
print(f"{GREEN}Task {task['order_index'] + 1} completed{RESET}")
|
|
315
|
+
else:
|
|
316
|
+
update_task_status(task['id'], 'failed')
|
|
317
|
+
print(f"{RED}Task {task['order_index'] + 1} failed{RESET}")
|
|
318
|
+
|
|
319
|
+
# Append list of files modified during the subagent session
|
|
320
|
+
subagent_session_id = result.pop('subagent_session_id', None)
|
|
321
|
+
if subagent_session_id is not None:
|
|
322
|
+
session_files = get_all_session_files_chain(subagent_session_id)
|
|
323
|
+
if session_files:
|
|
324
|
+
files_summary = "\n\nFiles modified in this task session:"
|
|
325
|
+
for sf in session_files:
|
|
326
|
+
files_summary += f"\n - [{sf['operation']}] {sf['file_path']}"
|
|
327
|
+
result['content'] = result['content'] + files_summary
|
|
328
|
+
|
|
329
|
+
return result
|
|
330
|
+
|
|
331
|
+
except Exception as e:
|
|
332
|
+
return {
|
|
333
|
+
"role": "tool",
|
|
334
|
+
"tool_call_id": toolcall_id,
|
|
335
|
+
"content": f"Failed to execute task: {str(e)}",
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def handle_mark_task_complete(arguments, toolcall_id, parent_session_id=None):
|
|
340
|
+
"""Manually mark a task as completed."""
|
|
341
|
+
from Agent.chat_history_db import update_task_status, get_todo_tasks, delete_todo_list
|
|
342
|
+
|
|
343
|
+
task_id = arguments.get("task_id")
|
|
344
|
+
todo_list_id = arguments.get("todo_list_id")
|
|
345
|
+
|
|
346
|
+
if not task_id:
|
|
347
|
+
return {
|
|
348
|
+
"role": "tool",
|
|
349
|
+
"tool_call_id": toolcall_id,
|
|
350
|
+
"content": "Error: 'task_id' is required",
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
try:
|
|
354
|
+
update_task_status(task_id, 'completed')
|
|
355
|
+
print(f"{GREEN}Task {task_id} marked as completed{RESET}")
|
|
356
|
+
|
|
357
|
+
# If we know the todo_list_id, check if all tasks are now done
|
|
358
|
+
if todo_list_id:
|
|
359
|
+
tasks = get_todo_tasks(todo_list_id)
|
|
360
|
+
all_done = all(t['status'] == 'completed' for t in tasks)
|
|
361
|
+
if all_done:
|
|
362
|
+
delete_todo_list(todo_list_id)
|
|
363
|
+
print(f"{GREEN}All tasks completed! Todo list {todo_list_id} has been removed.{RESET}")
|
|
364
|
+
return {
|
|
365
|
+
"role": "tool",
|
|
366
|
+
"tool_call_id": toolcall_id,
|
|
367
|
+
"content": f"Task {task_id} marked as completed. All tasks done -- todo list has been removed.",
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return {
|
|
371
|
+
"role": "tool",
|
|
372
|
+
"tool_call_id": toolcall_id,
|
|
373
|
+
"content": f"Task {task_id} marked as completed",
|
|
374
|
+
}
|
|
375
|
+
except Exception as e:
|
|
376
|
+
return {
|
|
377
|
+
"role": "tool",
|
|
378
|
+
"tool_call_id": toolcall_id,
|
|
379
|
+
"content": f"Failed to mark task as completed: {str(e)}",
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def handle_mark_task_failed(arguments, toolcall_id, parent_session_id=None):
|
|
384
|
+
"""Manually mark a task as failed."""
|
|
385
|
+
from Agent.chat_history_db import update_task_status
|
|
386
|
+
|
|
387
|
+
task_id = arguments.get("task_id")
|
|
388
|
+
|
|
389
|
+
if not task_id:
|
|
390
|
+
return {
|
|
391
|
+
"role": "tool",
|
|
392
|
+
"tool_call_id": toolcall_id,
|
|
393
|
+
"content": "Error: 'task_id' is required",
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
try:
|
|
397
|
+
update_task_status(task_id, 'failed')
|
|
398
|
+
print(f"{RED}Task {task_id} marked as failed{RESET}")
|
|
399
|
+
return {
|
|
400
|
+
"role": "tool",
|
|
401
|
+
"tool_call_id": toolcall_id,
|
|
402
|
+
"content": f"Task {task_id} marked as failed",
|
|
403
|
+
}
|
|
404
|
+
except Exception as e:
|
|
405
|
+
return {
|
|
406
|
+
"role": "tool",
|
|
407
|
+
"tool_call_id": toolcall_id,
|
|
408
|
+
"content": f"Failed to mark task as failed: {str(e)}",
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def handle_mark_task_cancelled(arguments, toolcall_id, parent_session_id=None):
|
|
413
|
+
"""Manually mark a task as cancelled. A reason is required."""
|
|
414
|
+
from Agent.chat_history_db import update_task_status
|
|
415
|
+
|
|
416
|
+
task_id = arguments.get("task_id")
|
|
417
|
+
reason = arguments.get("reason")
|
|
418
|
+
|
|
419
|
+
if not task_id:
|
|
420
|
+
return {
|
|
421
|
+
"role": "tool",
|
|
422
|
+
"tool_call_id": toolcall_id,
|
|
423
|
+
"content": "Error: 'task_id' is required",
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if not reason or not reason.strip():
|
|
427
|
+
return {
|
|
428
|
+
"role": "tool",
|
|
429
|
+
"tool_call_id": toolcall_id,
|
|
430
|
+
"content": "Error: 'reason' is required when cancelling a task. Provide a brief explanation of why this task is being cancelled.",
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
try:
|
|
434
|
+
update_task_status(task_id, 'cancelled', cancel_reason=reason.strip())
|
|
435
|
+
print(f"{YELLOW}Task {task_id} marked as cancelled. Reason: {reason.strip()}{RESET}")
|
|
436
|
+
return {
|
|
437
|
+
"role": "tool",
|
|
438
|
+
"tool_call_id": toolcall_id,
|
|
439
|
+
"content": f"Task {task_id} marked as cancelled. Reason: {reason.strip()}",
|
|
440
|
+
}
|
|
441
|
+
except Exception as e:
|
|
442
|
+
return {
|
|
443
|
+
"role": "tool",
|
|
444
|
+
"tool_call_id": toolcall_id,
|
|
445
|
+
"content": f"Failed to mark task as cancelled: {str(e)}",
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def handle_get_active_todo_list(arguments, toolcall_id, parent_session_id=None):
|
|
450
|
+
"""Get the active todo list for the current session."""
|
|
451
|
+
from Agent.chat_history_db import get_active_todo_list, resolve_todo_session_id
|
|
452
|
+
|
|
453
|
+
if parent_session_id is None:
|
|
454
|
+
return {
|
|
455
|
+
"role": "tool",
|
|
456
|
+
"tool_call_id": toolcall_id,
|
|
457
|
+
"content": "Error: parent_session_id is required",
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
todo_session_id = resolve_todo_session_id(parent_session_id)
|
|
461
|
+
|
|
462
|
+
try:
|
|
463
|
+
todo_list = get_active_todo_list(todo_session_id)
|
|
464
|
+
if todo_list:
|
|
465
|
+
return {
|
|
466
|
+
"role": "tool",
|
|
467
|
+
"tool_call_id": toolcall_id,
|
|
468
|
+
"content": f"Active todo list ID: {todo_list['id']}, Status: {todo_list['status']}",
|
|
469
|
+
}
|
|
470
|
+
else:
|
|
471
|
+
return {
|
|
472
|
+
"role": "tool",
|
|
473
|
+
"tool_call_id": toolcall_id,
|
|
474
|
+
"content": "No active todo list found for this chat",
|
|
475
|
+
}
|
|
476
|
+
except Exception as e:
|
|
477
|
+
return {
|
|
478
|
+
"role": "tool",
|
|
479
|
+
"tool_call_id": toolcall_id,
|
|
480
|
+
"content": f"Failed to get active todo list: {str(e)}",
|
|
481
|
+
}
|
Tools/utils.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from functools import lru_cache
|
|
4
|
+
from pathspec import PathSpec
|
|
5
|
+
from pathspec.patterns import GitWildMatchPattern
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def remove_em_dashes(text: str) -> str:
|
|
10
|
+
return text.replace(" — ", ", ").replace("—", ", ")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
BLUE = "\033[34m"
|
|
14
|
+
GREEN = "\033[32m"
|
|
15
|
+
YELLOW = "\033[33m"
|
|
16
|
+
RED = "\033[31m"
|
|
17
|
+
GRAY = "\033[90m"
|
|
18
|
+
RESET = "\033[0m"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def is_within_cwd(path: str) -> bool:
|
|
23
|
+
"""Check if a path resolves to within the current working directory.
|
|
24
|
+
|
|
25
|
+
Uses realpath to resolve symlinks, preventing escape via symlink tricks.
|
|
26
|
+
"""
|
|
27
|
+
cwd = os.path.realpath(os.getcwd())
|
|
28
|
+
resolved = os.path.realpath(os.path.abspath(path))
|
|
29
|
+
return resolved == cwd or resolved.startswith(cwd + os.sep)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@lru_cache(maxsize=1)
|
|
34
|
+
def _load_ignore_spec(cwd: str):
|
|
35
|
+
"""Load ignore patterns from .aiignore, falling back to .gitignore.
|
|
36
|
+
|
|
37
|
+
Returns a PathSpec instance. Cached per working directory.
|
|
38
|
+
"""
|
|
39
|
+
root = Path(cwd)
|
|
40
|
+
aiignore_path = root / '.aiignore'
|
|
41
|
+
gitignore_path = root / '.gitignore'
|
|
42
|
+
|
|
43
|
+
ignore_path = None
|
|
44
|
+
if aiignore_path.exists():
|
|
45
|
+
ignore_path = aiignore_path
|
|
46
|
+
elif gitignore_path.exists():
|
|
47
|
+
ignore_path = gitignore_path
|
|
48
|
+
|
|
49
|
+
if ignore_path is None:
|
|
50
|
+
return PathSpec.from_lines(GitWildMatchPattern, [])
|
|
51
|
+
|
|
52
|
+
with open(ignore_path, 'r', encoding='utf-8') as f:
|
|
53
|
+
patterns = f.read().splitlines()
|
|
54
|
+
|
|
55
|
+
return PathSpec.from_lines(GitWildMatchPattern, patterns)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def is_ignored(file_path: str) -> bool:
|
|
60
|
+
"""Check if a file path is ignored by .aiignore (or .gitignore as fallback).
|
|
61
|
+
|
|
62
|
+
If a .aiignore file exists in the project root, its patterns are used.
|
|
63
|
+
Otherwise, .gitignore is used as a fallback.
|
|
64
|
+
Returns False if neither file exists.
|
|
65
|
+
"""
|
|
66
|
+
cwd = os.getcwd()
|
|
67
|
+
spec = _load_ignore_spec(cwd)
|
|
68
|
+
rel = os.path.relpath(os.path.abspath(file_path), cwd)
|
|
69
|
+
return spec.match_file(rel)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def is_ignored_by_gitignore(file_path: str) -> bool:
|
|
74
|
+
"""Backward-compatible alias for is_ignored."""
|
|
75
|
+
return is_ignored(file_path)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def reindex_after_change(code_indexer):
|
|
80
|
+
"""Re-index the codebase after a file-modifying tool call.
|
|
81
|
+
|
|
82
|
+
Silently skips if code_indexer is None or re-indexing fails.
|
|
83
|
+
"""
|
|
84
|
+
if code_indexer is None:
|
|
85
|
+
return
|
|
86
|
+
try:
|
|
87
|
+
code_indexer.index_directory()
|
|
88
|
+
except (KeyboardInterrupt, EOFError):
|
|
89
|
+
print(f"{YELLOW}Re-indexing interrupted. Using existing index.{RESET}")
|
|
90
|
+
except Exception as e:
|
|
91
|
+
print(f"{YELLOW}Warning: Failed to re-index after tool execution: {e}{RESET}")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def auto_record_change(session_id, file_path: str, change_type: str, description: str, details: str = None):
|
|
96
|
+
"""Auto-record a change to the changes database.
|
|
97
|
+
|
|
98
|
+
Looks up the role from the session and records the change.
|
|
99
|
+
Silently fails if the session or database is unavailable.
|
|
100
|
+
"""
|
|
101
|
+
if session_id is None:
|
|
102
|
+
return
|
|
103
|
+
try:
|
|
104
|
+
from Agent.chat_history_db import add_change, get_session_role
|
|
105
|
+
role = get_session_role(session_id) or "unknown"
|
|
106
|
+
add_change(
|
|
107
|
+
prompt_id=str(session_id),
|
|
108
|
+
role=role,
|
|
109
|
+
session_id=session_id,
|
|
110
|
+
change_type=change_type,
|
|
111
|
+
file_path=file_path,
|
|
112
|
+
description=description,
|
|
113
|
+
details=details,
|
|
114
|
+
)
|
|
115
|
+
except Exception as e:
|
|
116
|
+
print(f"{RED}Warning: Failed to record change: {e}{RESET}")
|