feat(tools): add bulk operations support to todo tools
- update_todo: add `updates` param for bulk updates in one call - mark_todo_done: add `todo_ids` param to mark multiple todos done - mark_todo_pending: add `todo_ids` param to mark multiple pending - delete_todo: add `todo_ids` param to delete multiple todos - Increase todo renderer display limit from 10 to 25 - Maintains backward compatibility with single-ID usage - Update prompts to keep todos short-horizon and dynamic
This commit is contained in:
@@ -114,11 +114,13 @@ OPERATIONAL PRINCIPLES:
|
|||||||
TASK TRACKING:
|
TASK TRACKING:
|
||||||
- USE THE TODO TOOL EXTENSIVELY - this is critical for staying organized and focused
|
- USE THE TODO TOOL EXTENSIVELY - this is critical for staying organized and focused
|
||||||
- Each subagent has their own INDEPENDENT todo list - your todos are private to you
|
- Each subagent has their own INDEPENDENT todo list - your todos are private to you
|
||||||
- At the START of any task: Create todos to break down your work into clear steps
|
- KEEP THE LIST SHORT-HORIZON: track only the next few concrete steps (3-6 max), not long-term goals.
|
||||||
|
- REWRITE TODOS AS YOU LEARN: update, trim, or reprioritize the list whenever plans change or tasks finish.
|
||||||
|
- At the START of any task: Create todos to break down your next steps into clear actions
|
||||||
- BEFORE starting a task: Mark it as "in_progress" - this shows what you're actively doing
|
- BEFORE starting a task: Mark it as "in_progress" - this shows what you're actively doing
|
||||||
- AFTER completing a task: Mark it as "done" immediately - don't wait
|
- AFTER completing a task: Mark it as "done" immediately - don't wait
|
||||||
- When you discover new tasks: Add them as todos right away
|
- When you discover new tasks: Add them as todos right away and reprioritize; avoid dumping the whole project plan upfront
|
||||||
- ALWAYS follow this workflow: create → in_progress → done
|
- ALWAYS follow this workflow: create → in_progress → done, iterating frequently
|
||||||
- A well-maintained todo list prevents going in circles, forgetting tasks, and losing focus
|
- A well-maintained todo list prevents going in circles, forgetting tasks, and losing focus
|
||||||
- If you're unsure what to do next: Check your todo list first
|
- If you're unsure what to do next: Check your todo list first
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ def _truncate(text: str, length: int = 80) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def _format_todo_lines(
|
def _format_todo_lines(
|
||||||
cls: type[BaseToolRenderer], result: dict[str, Any], limit: int = 10
|
cls: type[BaseToolRenderer], result: dict[str, Any], limit: int = 25
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
todos = result.get("todos")
|
todos = result.get("todos")
|
||||||
if not isinstance(todos, list) or not todos:
|
if not isinstance(todos, list) or not todos:
|
||||||
@@ -67,7 +67,7 @@ class CreateTodoRenderer(BaseToolRenderer):
|
|||||||
if result and isinstance(result, dict):
|
if result and isinstance(result, dict):
|
||||||
if result.get("success"):
|
if result.get("success"):
|
||||||
lines = [header]
|
lines = [header]
|
||||||
lines.extend(_format_todo_lines(cls, result, limit=10))
|
lines.extend(_format_todo_lines(cls, result))
|
||||||
content_text = "\n".join(lines)
|
content_text = "\n".join(lines)
|
||||||
else:
|
else:
|
||||||
error = result.get("error", "Failed to create todo")
|
error = result.get("error", "Failed to create todo")
|
||||||
@@ -92,7 +92,7 @@ class ListTodosRenderer(BaseToolRenderer):
|
|||||||
if result and isinstance(result, dict):
|
if result and isinstance(result, dict):
|
||||||
if result.get("success"):
|
if result.get("success"):
|
||||||
lines = [header]
|
lines = [header]
|
||||||
lines.extend(_format_todo_lines(cls, result, limit=10))
|
lines.extend(_format_todo_lines(cls, result))
|
||||||
content_text = "\n".join(lines)
|
content_text = "\n".join(lines)
|
||||||
else:
|
else:
|
||||||
error = result.get("error", "Unable to list todos")
|
error = result.get("error", "Unable to list todos")
|
||||||
@@ -117,7 +117,7 @@ class UpdateTodoRenderer(BaseToolRenderer):
|
|||||||
if result and isinstance(result, dict):
|
if result and isinstance(result, dict):
|
||||||
if result.get("success"):
|
if result.get("success"):
|
||||||
lines = [header]
|
lines = [header]
|
||||||
lines.extend(_format_todo_lines(cls, result, limit=10))
|
lines.extend(_format_todo_lines(cls, result))
|
||||||
content_text = "\n".join(lines)
|
content_text = "\n".join(lines)
|
||||||
else:
|
else:
|
||||||
error = result.get("error", "Failed to update todo")
|
error = result.get("error", "Failed to update todo")
|
||||||
@@ -142,7 +142,7 @@ class MarkTodoDoneRenderer(BaseToolRenderer):
|
|||||||
if result and isinstance(result, dict):
|
if result and isinstance(result, dict):
|
||||||
if result.get("success"):
|
if result.get("success"):
|
||||||
lines = [header]
|
lines = [header]
|
||||||
lines.extend(_format_todo_lines(cls, result, limit=10))
|
lines.extend(_format_todo_lines(cls, result))
|
||||||
content_text = "\n".join(lines)
|
content_text = "\n".join(lines)
|
||||||
else:
|
else:
|
||||||
error = result.get("error", "Failed to mark todo done")
|
error = result.get("error", "Failed to mark todo done")
|
||||||
@@ -167,7 +167,7 @@ class MarkTodoPendingRenderer(BaseToolRenderer):
|
|||||||
if result and isinstance(result, dict):
|
if result and isinstance(result, dict):
|
||||||
if result.get("success"):
|
if result.get("success"):
|
||||||
lines = [header]
|
lines = [header]
|
||||||
lines.extend(_format_todo_lines(cls, result, limit=10))
|
lines.extend(_format_todo_lines(cls, result))
|
||||||
content_text = "\n".join(lines)
|
content_text = "\n".join(lines)
|
||||||
else:
|
else:
|
||||||
error = result.get("error", "Failed to reopen todo")
|
error = result.get("error", "Failed to reopen todo")
|
||||||
@@ -192,7 +192,7 @@ class DeleteTodoRenderer(BaseToolRenderer):
|
|||||||
if result and isinstance(result, dict):
|
if result and isinstance(result, dict):
|
||||||
if result.get("success"):
|
if result.get("success"):
|
||||||
lines = [header]
|
lines = [header]
|
||||||
lines.extend(_format_todo_lines(cls, result, limit=10))
|
lines.extend(_format_todo_lines(cls, result))
|
||||||
content_text = "\n".join(lines)
|
content_text = "\n".join(lines)
|
||||||
else:
|
else:
|
||||||
error = result.get("error", "Failed to remove todo")
|
error = result.get("error", "Failed to remove todo")
|
||||||
|
|||||||
@@ -47,6 +47,70 @@ def _sorted_todos(agent_id: str) -> list[dict[str, Any]]:
|
|||||||
return todos_list
|
return todos_list
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_todo_ids(raw_ids: Any) -> list[str]:
|
||||||
|
if raw_ids is None:
|
||||||
|
return []
|
||||||
|
|
||||||
|
if isinstance(raw_ids, str):
|
||||||
|
stripped = raw_ids.strip()
|
||||||
|
if not stripped:
|
||||||
|
return []
|
||||||
|
try:
|
||||||
|
data = json.loads(stripped)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
data = stripped.split(",") if "," in stripped else [stripped]
|
||||||
|
if isinstance(data, list):
|
||||||
|
return [str(item).strip() for item in data if str(item).strip()]
|
||||||
|
return [str(data).strip()]
|
||||||
|
|
||||||
|
if isinstance(raw_ids, list):
|
||||||
|
return [str(item).strip() for item in raw_ids if str(item).strip()]
|
||||||
|
|
||||||
|
return [str(raw_ids).strip()]
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_bulk_updates(raw_updates: Any) -> list[dict[str, Any]]:
|
||||||
|
if raw_updates is None:
|
||||||
|
return []
|
||||||
|
|
||||||
|
data = raw_updates
|
||||||
|
if isinstance(raw_updates, str):
|
||||||
|
stripped = raw_updates.strip()
|
||||||
|
if not stripped:
|
||||||
|
return []
|
||||||
|
try:
|
||||||
|
data = json.loads(stripped)
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
raise ValueError("Updates must be valid JSON") from e
|
||||||
|
|
||||||
|
if isinstance(data, dict):
|
||||||
|
data = [data]
|
||||||
|
|
||||||
|
if not isinstance(data, list):
|
||||||
|
raise TypeError("Updates must be a list of update objects")
|
||||||
|
|
||||||
|
normalized: list[dict[str, Any]] = []
|
||||||
|
for item in data:
|
||||||
|
if not isinstance(item, dict):
|
||||||
|
raise TypeError("Each update must be an object with todo_id")
|
||||||
|
|
||||||
|
todo_id = item.get("todo_id") or item.get("id")
|
||||||
|
if not todo_id:
|
||||||
|
raise ValueError("Each update must include 'todo_id'")
|
||||||
|
|
||||||
|
normalized.append(
|
||||||
|
{
|
||||||
|
"todo_id": str(todo_id).strip(),
|
||||||
|
"title": item.get("title"),
|
||||||
|
"description": item.get("description"),
|
||||||
|
"priority": item.get("priority"),
|
||||||
|
"status": item.get("status"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return normalized
|
||||||
|
|
||||||
|
|
||||||
def _normalize_bulk_todos(raw_todos: Any) -> list[dict[str, Any]]:
|
def _normalize_bulk_todos(raw_todos: Any) -> list[dict[str, Any]]:
|
||||||
if raw_todos is None:
|
if raw_todos is None:
|
||||||
return []
|
return []
|
||||||
@@ -233,27 +297,22 @@ def list_todos(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@register_tool(sandbox_execution=False)
|
def _apply_single_update(
|
||||||
def update_todo(
|
agent_todos: dict[str, dict[str, Any]],
|
||||||
agent_state: Any,
|
|
||||||
todo_id: str,
|
todo_id: str,
|
||||||
title: str | None = None,
|
title: str | None = None,
|
||||||
description: str | None = None,
|
description: str | None = None,
|
||||||
priority: str | None = None,
|
priority: str | None = None,
|
||||||
status: str | None = None,
|
status: str | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any] | None:
|
||||||
try:
|
|
||||||
agent_id = agent_state.agent_id
|
|
||||||
agent_todos = _get_agent_todos(agent_id)
|
|
||||||
|
|
||||||
if todo_id not in agent_todos:
|
if todo_id not in agent_todos:
|
||||||
return {"success": False, "error": f"Todo with ID '{todo_id}' not found"}
|
return {"todo_id": todo_id, "error": f"Todo with ID '{todo_id}' not found"}
|
||||||
|
|
||||||
todo = agent_todos[todo_id]
|
todo = agent_todos[todo_id]
|
||||||
|
|
||||||
if title is not None:
|
if title is not None:
|
||||||
if not title.strip():
|
if not title.strip():
|
||||||
return {"success": False, "error": "Title cannot be empty"}
|
return {"todo_id": todo_id, "error": "Title cannot be empty"}
|
||||||
todo["title"] = title.strip()
|
todo["title"] = title.strip()
|
||||||
|
|
||||||
if description is not None:
|
if description is not None:
|
||||||
@@ -261,17 +320,15 @@ def update_todo(
|
|||||||
|
|
||||||
if priority is not None:
|
if priority is not None:
|
||||||
try:
|
try:
|
||||||
todo["priority"] = _normalize_priority(
|
todo["priority"] = _normalize_priority(priority, str(todo.get("priority", "normal")))
|
||||||
priority, str(todo.get("priority", "normal"))
|
|
||||||
)
|
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
return {"success": False, "error": str(exc)}
|
return {"todo_id": todo_id, "error": str(exc)}
|
||||||
|
|
||||||
if status is not None:
|
if status is not None:
|
||||||
status_candidate = status.lower()
|
status_candidate = status.lower()
|
||||||
if status_candidate not in VALID_STATUSES:
|
if status_candidate not in VALID_STATUSES:
|
||||||
return {
|
return {
|
||||||
"success": False,
|
"todo_id": todo_id,
|
||||||
"error": f"Invalid status. Must be one of: {', '.join(VALID_STATUSES)}",
|
"error": f"Invalid status. Must be one of: {', '.join(VALID_STATUSES)}",
|
||||||
}
|
}
|
||||||
todo["status"] = status_candidate
|
todo["status"] = status_candidate
|
||||||
@@ -281,98 +338,231 @@ def update_todo(
|
|||||||
todo["completed_at"] = None
|
todo["completed_at"] = None
|
||||||
|
|
||||||
todo["updated_at"] = datetime.now(UTC).isoformat()
|
todo["updated_at"] = datetime.now(UTC).isoformat()
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@register_tool(sandbox_execution=False)
|
||||||
|
def update_todo(
|
||||||
|
agent_state: Any,
|
||||||
|
todo_id: str | None = None,
|
||||||
|
title: str | None = None,
|
||||||
|
description: str | None = None,
|
||||||
|
priority: str | None = None,
|
||||||
|
status: str | None = None,
|
||||||
|
updates: Any | None = None,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
try:
|
||||||
|
agent_id = agent_state.agent_id
|
||||||
|
agent_todos = _get_agent_todos(agent_id)
|
||||||
|
|
||||||
|
updates_to_apply: list[dict[str, Any]] = []
|
||||||
|
|
||||||
|
if updates is not None:
|
||||||
|
updates_to_apply.extend(_normalize_bulk_updates(updates))
|
||||||
|
|
||||||
|
if todo_id is not None:
|
||||||
|
updates_to_apply.append(
|
||||||
|
{
|
||||||
|
"todo_id": todo_id,
|
||||||
|
"title": title,
|
||||||
|
"description": description,
|
||||||
|
"priority": priority,
|
||||||
|
"status": status,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if not updates_to_apply:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": "Provide todo_id or 'updates' list to update.",
|
||||||
|
}
|
||||||
|
|
||||||
|
updated: list[str] = []
|
||||||
|
errors: list[dict[str, Any]] = []
|
||||||
|
|
||||||
|
for update in updates_to_apply:
|
||||||
|
error = _apply_single_update(
|
||||||
|
agent_todos,
|
||||||
|
update["todo_id"],
|
||||||
|
update.get("title"),
|
||||||
|
update.get("description"),
|
||||||
|
update.get("priority"),
|
||||||
|
update.get("status"),
|
||||||
|
)
|
||||||
|
if error:
|
||||||
|
errors.append(error)
|
||||||
|
else:
|
||||||
|
updated.append(update["todo_id"])
|
||||||
|
|
||||||
todos_list = _sorted_todos(agent_id)
|
todos_list = _sorted_todos(agent_id)
|
||||||
|
|
||||||
return {
|
response: dict[str, Any] = {
|
||||||
"success": True,
|
"success": len(errors) == 0,
|
||||||
|
"updated": updated,
|
||||||
|
"updated_count": len(updated),
|
||||||
"todos": todos_list,
|
"todos": todos_list,
|
||||||
"total_count": len(todos_list),
|
"total_count": len(todos_list),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
response["errors"] = errors
|
||||||
|
|
||||||
except (ValueError, TypeError) as e:
|
except (ValueError, TypeError) as e:
|
||||||
return {"success": False, "error": str(e)}
|
return {"success": False, "error": str(e)}
|
||||||
|
else:
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
@register_tool(sandbox_execution=False)
|
@register_tool(sandbox_execution=False)
|
||||||
def mark_todo_done(
|
def mark_todo_done(
|
||||||
agent_state: Any,
|
agent_state: Any,
|
||||||
todo_id: str,
|
todo_id: str | None = None,
|
||||||
|
todo_ids: Any | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
try:
|
try:
|
||||||
agent_id = agent_state.agent_id
|
agent_id = agent_state.agent_id
|
||||||
agent_todos = _get_agent_todos(agent_id)
|
agent_todos = _get_agent_todos(agent_id)
|
||||||
|
|
||||||
if todo_id not in agent_todos:
|
ids_to_mark: list[str] = []
|
||||||
return {"success": False, "error": f"Todo with ID '{todo_id}' not found"}
|
if todo_ids is not None:
|
||||||
|
ids_to_mark.extend(_normalize_todo_ids(todo_ids))
|
||||||
|
if todo_id is not None:
|
||||||
|
ids_to_mark.append(todo_id)
|
||||||
|
|
||||||
todo = agent_todos[todo_id]
|
if not ids_to_mark:
|
||||||
|
return {"success": False, "error": "Provide todo_id or todo_ids to mark as done."}
|
||||||
|
|
||||||
|
marked: list[str] = []
|
||||||
|
errors: list[dict[str, Any]] = []
|
||||||
|
timestamp = datetime.now(UTC).isoformat()
|
||||||
|
|
||||||
|
for tid in ids_to_mark:
|
||||||
|
if tid not in agent_todos:
|
||||||
|
errors.append({"todo_id": tid, "error": f"Todo with ID '{tid}' not found"})
|
||||||
|
continue
|
||||||
|
|
||||||
|
todo = agent_todos[tid]
|
||||||
todo["status"] = "done"
|
todo["status"] = "done"
|
||||||
todo["completed_at"] = datetime.now(UTC).isoformat()
|
todo["completed_at"] = timestamp
|
||||||
todo["updated_at"] = datetime.now(UTC).isoformat()
|
todo["updated_at"] = timestamp
|
||||||
|
marked.append(tid)
|
||||||
|
|
||||||
todos_list = _sorted_todos(agent_id)
|
todos_list = _sorted_todos(agent_id)
|
||||||
|
|
||||||
return {
|
response: dict[str, Any] = {
|
||||||
"success": True,
|
"success": len(errors) == 0,
|
||||||
|
"marked_done": marked,
|
||||||
|
"marked_count": len(marked),
|
||||||
"todos": todos_list,
|
"todos": todos_list,
|
||||||
"total_count": len(todos_list),
|
"total_count": len(todos_list),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
response["errors"] = errors
|
||||||
|
|
||||||
except (ValueError, TypeError) as e:
|
except (ValueError, TypeError) as e:
|
||||||
return {"success": False, "error": str(e)}
|
return {"success": False, "error": str(e)}
|
||||||
|
else:
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
@register_tool(sandbox_execution=False)
|
@register_tool(sandbox_execution=False)
|
||||||
def mark_todo_pending(
|
def mark_todo_pending(
|
||||||
agent_state: Any,
|
agent_state: Any,
|
||||||
todo_id: str,
|
todo_id: str | None = None,
|
||||||
|
todo_ids: Any | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
try:
|
try:
|
||||||
agent_id = agent_state.agent_id
|
agent_id = agent_state.agent_id
|
||||||
agent_todos = _get_agent_todos(agent_id)
|
agent_todos = _get_agent_todos(agent_id)
|
||||||
|
|
||||||
if todo_id not in agent_todos:
|
ids_to_mark: list[str] = []
|
||||||
return {"success": False, "error": f"Todo with ID '{todo_id}' not found"}
|
if todo_ids is not None:
|
||||||
|
ids_to_mark.extend(_normalize_todo_ids(todo_ids))
|
||||||
|
if todo_id is not None:
|
||||||
|
ids_to_mark.append(todo_id)
|
||||||
|
|
||||||
todo = agent_todos[todo_id]
|
if not ids_to_mark:
|
||||||
|
return {"success": False, "error": "Provide todo_id or todo_ids to mark as pending."}
|
||||||
|
|
||||||
|
marked: list[str] = []
|
||||||
|
errors: list[dict[str, Any]] = []
|
||||||
|
timestamp = datetime.now(UTC).isoformat()
|
||||||
|
|
||||||
|
for tid in ids_to_mark:
|
||||||
|
if tid not in agent_todos:
|
||||||
|
errors.append({"todo_id": tid, "error": f"Todo with ID '{tid}' not found"})
|
||||||
|
continue
|
||||||
|
|
||||||
|
todo = agent_todos[tid]
|
||||||
todo["status"] = "pending"
|
todo["status"] = "pending"
|
||||||
todo["completed_at"] = None
|
todo["completed_at"] = None
|
||||||
todo["updated_at"] = datetime.now(UTC).isoformat()
|
todo["updated_at"] = timestamp
|
||||||
|
marked.append(tid)
|
||||||
|
|
||||||
todos_list = _sorted_todos(agent_id)
|
todos_list = _sorted_todos(agent_id)
|
||||||
|
|
||||||
return {
|
response: dict[str, Any] = {
|
||||||
"success": True,
|
"success": len(errors) == 0,
|
||||||
|
"marked_pending": marked,
|
||||||
|
"marked_count": len(marked),
|
||||||
"todos": todos_list,
|
"todos": todos_list,
|
||||||
"total_count": len(todos_list),
|
"total_count": len(todos_list),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
response["errors"] = errors
|
||||||
|
|
||||||
except (ValueError, TypeError) as e:
|
except (ValueError, TypeError) as e:
|
||||||
return {"success": False, "error": str(e)}
|
return {"success": False, "error": str(e)}
|
||||||
|
else:
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
@register_tool(sandbox_execution=False)
|
@register_tool(sandbox_execution=False)
|
||||||
def delete_todo(
|
def delete_todo(
|
||||||
agent_state: Any,
|
agent_state: Any,
|
||||||
todo_id: str,
|
todo_id: str | None = None,
|
||||||
|
todo_ids: Any | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
try:
|
try:
|
||||||
agent_id = agent_state.agent_id
|
agent_id = agent_state.agent_id
|
||||||
agent_todos = _get_agent_todos(agent_id)
|
agent_todos = _get_agent_todos(agent_id)
|
||||||
|
|
||||||
if todo_id not in agent_todos:
|
ids_to_delete: list[str] = []
|
||||||
return {"success": False, "error": f"Todo with ID '{todo_id}' not found"}
|
if todo_ids is not None:
|
||||||
|
ids_to_delete.extend(_normalize_todo_ids(todo_ids))
|
||||||
|
if todo_id is not None:
|
||||||
|
ids_to_delete.append(todo_id)
|
||||||
|
|
||||||
del agent_todos[todo_id]
|
if not ids_to_delete:
|
||||||
|
return {"success": False, "error": "Provide todo_id or todo_ids to delete."}
|
||||||
|
|
||||||
|
deleted: list[str] = []
|
||||||
|
errors: list[dict[str, Any]] = []
|
||||||
|
|
||||||
|
for tid in ids_to_delete:
|
||||||
|
if tid not in agent_todos:
|
||||||
|
errors.append({"todo_id": tid, "error": f"Todo with ID '{tid}' not found"})
|
||||||
|
continue
|
||||||
|
|
||||||
|
del agent_todos[tid]
|
||||||
|
deleted.append(tid)
|
||||||
|
|
||||||
todos_list = _sorted_todos(agent_id)
|
todos_list = _sorted_todos(agent_id)
|
||||||
|
|
||||||
return {
|
response: dict[str, Any] = {
|
||||||
"success": True,
|
"success": len(errors) == 0,
|
||||||
|
"deleted": deleted,
|
||||||
|
"deleted_count": len(deleted),
|
||||||
"todos": todos_list,
|
"todos": todos_list,
|
||||||
"total_count": len(todos_list),
|
"total_count": len(todos_list),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
response["errors"] = errors
|
||||||
|
|
||||||
except (ValueError, TypeError) as e:
|
except (ValueError, TypeError) as e:
|
||||||
return {"success": False, "error": str(e)}
|
return {"success": False, "error": str(e)}
|
||||||
|
else:
|
||||||
|
return response
|
||||||
|
|||||||
@@ -6,10 +6,11 @@
|
|||||||
do not interfere with other agents' todos. Use this to your advantage.
|
do not interfere with other agents' todos. Use this to your advantage.
|
||||||
|
|
||||||
WORKFLOW - Follow this for EVERY task:
|
WORKFLOW - Follow this for EVERY task:
|
||||||
1. Create todos at the START to break down your work
|
1. Keep the list short-horizon: track only the next few concrete steps (3-6 max), not long-term goals.
|
||||||
2. BEFORE starting a task: Mark it as "in_progress" using update_todo
|
2. Create/update todos as you learn new info; drop or rewrite items when plans change.
|
||||||
3. AFTER completing a task: Mark it as "done" using mark_todo_done
|
3. BEFORE starting a task: Mark it as "in_progress" using update_todo.
|
||||||
4. When you discover new tasks: Add them as todos right away
|
4. AFTER completing a task: Mark it as "done" using mark_todo_done.
|
||||||
|
5. When you discover new tasks: Add them right away and re-prioritize; avoid giant upfront lists.
|
||||||
|
|
||||||
ALWAYS mark the current task as in_progress before working on it. This shows what you're
|
ALWAYS mark the current task as in_progress before working on it. This shows what you're
|
||||||
actively doing. Then mark it done when finished. Never skip these status updates.
|
actively doing. Then mark it done when finished. Never skip these status updates.
|
||||||
@@ -107,94 +108,122 @@
|
|||||||
</tool>
|
</tool>
|
||||||
|
|
||||||
<tool name="update_todo">
|
<tool name="update_todo">
|
||||||
<description>Update an existing todo item's title, description, priority, or status.</description>
|
<description>Update one or multiple todo items. Supports bulk updates in a single call.</description>
|
||||||
<parameters>
|
<parameters>
|
||||||
<parameter name="todo_id" type="string" required="true">
|
<parameter name="todo_id" type="string" required="false">
|
||||||
<description>ID of the todo to update</description>
|
<description>ID of a single todo to update (for simple updates)</description>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="updates" type="string" required="false">
|
||||||
|
<description>Bulk update multiple todos at once. JSON array of objects with todo_id and fields to update: [{"todo_id": "abc", "status": "done"}, {"todo_id": "def", "priority": "high"}]</description>
|
||||||
</parameter>
|
</parameter>
|
||||||
<parameter name="title" type="string" required="false">
|
<parameter name="title" type="string" required="false">
|
||||||
<description>New title for the todo</description>
|
<description>New title (used with todo_id)</description>
|
||||||
</parameter>
|
</parameter>
|
||||||
<parameter name="description" type="string" required="false">
|
<parameter name="description" type="string" required="false">
|
||||||
<description>New description for the todo</description>
|
<description>New description (used with todo_id)</description>
|
||||||
</parameter>
|
</parameter>
|
||||||
<parameter name="priority" type="string" required="false">
|
<parameter name="priority" type="string" required="false">
|
||||||
<description>New priority: "low", "normal", "high", "critical"</description>
|
<description>New priority: "low", "normal", "high", "critical" (used with todo_id)</description>
|
||||||
</parameter>
|
</parameter>
|
||||||
<parameter name="status" type="string" required="false">
|
<parameter name="status" type="string" required="false">
|
||||||
<description>New status: "pending", "in_progress", "done"</description>
|
<description>New status: "pending", "in_progress", "done" (used with todo_id)</description>
|
||||||
</parameter>
|
</parameter>
|
||||||
</parameters>
|
</parameters>
|
||||||
<returns type="Dict[str, Any]">
|
<returns type="Dict[str, Any]">
|
||||||
<description>Response containing: - success: Whether the update was successful</description>
|
<description>Response containing: - updated: List of updated todo IDs - updated_count: Number updated - todos: Full sorted todo list - errors: Any failed updates</description>
|
||||||
</returns>
|
</returns>
|
||||||
<examples>
|
<examples>
|
||||||
# Update priority and add description
|
# Single update
|
||||||
<function=update_todo>
|
|
||||||
<parameter=todo_id>abc123</parameter>
|
|
||||||
<parameter=priority>critical</parameter>
|
|
||||||
<parameter=description>Found potential RCE vector, needs immediate attention</parameter>
|
|
||||||
</function>
|
|
||||||
|
|
||||||
# Mark as in progress
|
|
||||||
<function=update_todo>
|
<function=update_todo>
|
||||||
<parameter=todo_id>abc123</parameter>
|
<parameter=todo_id>abc123</parameter>
|
||||||
<parameter=status>in_progress</parameter>
|
<parameter=status>in_progress</parameter>
|
||||||
|
</function>
|
||||||
|
|
||||||
|
# Bulk update - mark multiple todos with different statuses in ONE call
|
||||||
|
<function=update_todo>
|
||||||
|
<parameter=updates>[{"todo_id": "abc123", "status": "done"}, {"todo_id": "def456", "status": "in_progress"}, {"todo_id": "ghi789", "priority": "critical"}]</parameter>
|
||||||
</function>
|
</function>
|
||||||
</examples>
|
</examples>
|
||||||
</tool>
|
</tool>
|
||||||
|
|
||||||
<tool name="mark_todo_done">
|
<tool name="mark_todo_done">
|
||||||
<description>Mark a todo item as completed. DO THIS IMMEDIATELY after finishing a task.</description>
|
<description>Mark one or multiple todos as completed in a single call. DO THIS IMMEDIATELY after finishing tasks.</description>
|
||||||
<details>Mark todos as done right after completing them - don't wait! This keeps your list accurate,
|
<details>Mark todos as done right after completing them - don't wait! Supports marking multiple todos at once
|
||||||
helps track progress, and gives you a clear picture of what's been accomplished vs what remains.</details>
|
to save tool calls. This keeps your list accurate and gives you a clear picture of progress.</details>
|
||||||
<parameters>
|
<parameters>
|
||||||
<parameter name="todo_id" type="string" required="true">
|
<parameter name="todo_id" type="string" required="false">
|
||||||
<description>ID of the todo to mark as done</description>
|
<description>ID of a single todo to mark as done</description>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="todo_ids" type="string" required="false">
|
||||||
|
<description>Mark multiple todos done at once. JSON array of IDs: ["abc123", "def456"] or comma-separated: "abc123, def456"</description>
|
||||||
</parameter>
|
</parameter>
|
||||||
</parameters>
|
</parameters>
|
||||||
<returns type="Dict[str, Any]">
|
<returns type="Dict[str, Any]">
|
||||||
<description>Response containing: - success: Whether the operation was successful</description>
|
<description>Response containing: - marked_done: List of IDs marked done - marked_count: Number marked - todos: Full sorted list - errors: Any failures</description>
|
||||||
</returns>
|
</returns>
|
||||||
<examples>
|
<examples>
|
||||||
|
# Mark single todo done
|
||||||
<function=mark_todo_done>
|
<function=mark_todo_done>
|
||||||
<parameter=todo_id>abc123</parameter>
|
<parameter=todo_id>abc123</parameter>
|
||||||
|
</function>
|
||||||
|
|
||||||
|
# Mark multiple todos done in ONE call
|
||||||
|
<function=mark_todo_done>
|
||||||
|
<parameter=todo_ids>["abc123", "def456", "ghi789"]</parameter>
|
||||||
</function>
|
</function>
|
||||||
</examples>
|
</examples>
|
||||||
</tool>
|
</tool>
|
||||||
|
|
||||||
<tool name="mark_todo_pending">
|
<tool name="mark_todo_pending">
|
||||||
<description>Mark a todo item as pending (reopen a completed task).</description>
|
<description>Mark one or multiple todos as pending (reopen completed tasks).</description>
|
||||||
<details>Use this to reopen a task that was marked done but needs more work.</details>
|
<details>Use this to reopen tasks that were marked done but need more work. Supports bulk operations.</details>
|
||||||
<parameters>
|
<parameters>
|
||||||
<parameter name="todo_id" type="string" required="true">
|
<parameter name="todo_id" type="string" required="false">
|
||||||
<description>ID of the todo to mark as pending</description>
|
<description>ID of a single todo to mark as pending</description>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="todo_ids" type="string" required="false">
|
||||||
|
<description>Mark multiple todos pending at once. JSON array of IDs: ["abc123", "def456"] or comma-separated: "abc123, def456"</description>
|
||||||
</parameter>
|
</parameter>
|
||||||
</parameters>
|
</parameters>
|
||||||
<returns type="Dict[str, Any]">
|
<returns type="Dict[str, Any]">
|
||||||
<description>Response containing: - success: Whether the operation was successful</description>
|
<description>Response containing: - marked_pending: List of IDs marked pending - marked_count: Number marked - todos: Full sorted list - errors: Any failures</description>
|
||||||
</returns>
|
</returns>
|
||||||
<examples>
|
<examples>
|
||||||
|
# Mark single todo pending
|
||||||
<function=mark_todo_pending>
|
<function=mark_todo_pending>
|
||||||
<parameter=todo_id>abc123</parameter>
|
<parameter=todo_id>abc123</parameter>
|
||||||
|
</function>
|
||||||
|
|
||||||
|
# Mark multiple todos pending in ONE call
|
||||||
|
<function=mark_todo_pending>
|
||||||
|
<parameter=todo_ids>["abc123", "def456"]</parameter>
|
||||||
</function>
|
</function>
|
||||||
</examples>
|
</examples>
|
||||||
</tool>
|
</tool>
|
||||||
|
|
||||||
<tool name="delete_todo">
|
<tool name="delete_todo">
|
||||||
<description>Delete a todo item.</description>
|
<description>Delete one or multiple todos in a single call.</description>
|
||||||
<details>Use this to remove todos that are no longer relevant or were created by mistake.</details>
|
<details>Use this to remove todos that are no longer relevant. Supports bulk deletion to save tool calls.</details>
|
||||||
<parameters>
|
<parameters>
|
||||||
<parameter name="todo_id" type="string" required="true">
|
<parameter name="todo_id" type="string" required="false">
|
||||||
<description>ID of the todo to delete</description>
|
<description>ID of a single todo to delete</description>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="todo_ids" type="string" required="false">
|
||||||
|
<description>Delete multiple todos at once. JSON array of IDs: ["abc123", "def456"] or comma-separated: "abc123, def456"</description>
|
||||||
</parameter>
|
</parameter>
|
||||||
</parameters>
|
</parameters>
|
||||||
<returns type="Dict[str, Any]">
|
<returns type="Dict[str, Any]">
|
||||||
<description>Response containing: - success: Whether the deletion was successful</description>
|
<description>Response containing: - deleted: List of deleted IDs - deleted_count: Number deleted - todos: Remaining todos - errors: Any failures</description>
|
||||||
</returns>
|
</returns>
|
||||||
<examples>
|
<examples>
|
||||||
|
# Delete single todo
|
||||||
<function=delete_todo>
|
<function=delete_todo>
|
||||||
<parameter=todo_id>abc123</parameter>
|
<parameter=todo_id>abc123</parameter>
|
||||||
|
</function>
|
||||||
|
|
||||||
|
# Delete multiple todos in ONE call
|
||||||
|
<function=delete_todo>
|
||||||
|
<parameter=todo_ids>["abc123", "def456", "ghi789"]</parameter>
|
||||||
</function>
|
</function>
|
||||||
</examples>
|
</examples>
|
||||||
</tool>
|
</tool>
|
||||||
|
|||||||
Reference in New Issue
Block a user