Add update_task fields, list_tasks, and update_todo tools
- update_note: extend with status/priority/due_date fields so task attributes can be changed via chat (mark done, set priority, move due date). body is now optional — task field updates work without touching content. - list_tasks: new core tool with status/priority/due_before/due_after/limit filters backed by list_notes(is_task=True). Enables queries like "overdue tasks", "high priority tasks", "what's in progress". - update_todo: new CalDAV tool to modify VTODO summary, due date, description, and priority — follows update_event pattern (modify component, rebuild ical, save). Completes the CalDAV todo CRUD suite. - tools.py: add update_todo import + execute case (type: todo_updated) - llm.py: add list_tasks and update_todo to available actions + guidance - intent.py: routing rules for mark-done/priority/due-date → update_note, overdue/in-progress/high-priority queries → list_tasks, CalDAV todo updates → update_todo - ToolCallCard.vue: tasks list block (linked titles + due + priority badges), todo_updated label, tool-task-priority CSS classes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,7 @@ from fabledassistant.services.caldav import (
|
||||
list_todos,
|
||||
search_events,
|
||||
update_event,
|
||||
update_todo,
|
||||
)
|
||||
from fabledassistant.services.notes import create_note, get_note_by_title, list_notes, update_note
|
||||
from fabledassistant.services.tag_suggestions import suggest_tags
|
||||
@@ -79,8 +80,9 @@ _CORE_TOOLS = [
|
||||
"function": {
|
||||
"name": "update_note",
|
||||
"description": (
|
||||
"Update an existing note's content or title. "
|
||||
"Use this when the user asks to add to, edit, expand, flesh out, or modify a note that already exists. "
|
||||
"Update an existing note or task — content, title, status, priority, or due date. "
|
||||
"Use this when the user asks to add to, edit, expand, or modify a note, "
|
||||
"OR to mark a task done/in-progress, change its priority, or set a due date. "
|
||||
"NEVER use create_note when updating an existing note — always use update_note."
|
||||
),
|
||||
"parameters": {
|
||||
@@ -88,26 +90,40 @@ _CORE_TOOLS = [
|
||||
"properties": {
|
||||
"query": {
|
||||
"type": "string",
|
||||
"description": "Title or keyword to find the note to update",
|
||||
"description": "Title or keyword to find the note or task to update",
|
||||
},
|
||||
"body": {
|
||||
"type": "string",
|
||||
"description": "New note content in markdown",
|
||||
"description": "New note content in markdown (omit if only updating task fields)",
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "Optional new title for the note",
|
||||
"description": "Optional new title",
|
||||
},
|
||||
"mode": {
|
||||
"type": "string",
|
||||
"enum": ["replace", "append"],
|
||||
"description": (
|
||||
"How to apply the new body: 'replace' overwrites the existing content (default), "
|
||||
"'append' adds the new content after the existing content"
|
||||
"How to apply the new body: 'replace' overwrites existing content (default), "
|
||||
"'append' adds after existing content"
|
||||
),
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["todo", "in_progress", "done"],
|
||||
"description": "New task status. Use to mark a task done, start it, etc.",
|
||||
},
|
||||
"priority": {
|
||||
"type": "string",
|
||||
"enum": ["none", "low", "medium", "high"],
|
||||
"description": "New task priority",
|
||||
},
|
||||
"due_date": {
|
||||
"type": "string",
|
||||
"description": "New due date in YYYY-MM-DD format",
|
||||
},
|
||||
},
|
||||
"required": ["query", "body"],
|
||||
"required": ["query"],
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -128,6 +144,45 @@ _CORE_TOOLS = [
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "list_tasks",
|
||||
"description": (
|
||||
"List the user's tasks with optional filters. Use this when the user asks about their tasks "
|
||||
"by status (e.g. 'what's in progress'), priority (e.g. 'high priority tasks'), "
|
||||
"or due date (e.g. 'overdue tasks', 'due this week'). "
|
||||
"For 'overdue', set due_before to today's date. For 'due today', set due_after to today and due_before to tomorrow."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["todo", "in_progress", "done"],
|
||||
"description": "Filter by task status",
|
||||
},
|
||||
"priority": {
|
||||
"type": "string",
|
||||
"enum": ["none", "low", "medium", "high"],
|
||||
"description": "Filter by priority level",
|
||||
},
|
||||
"due_before": {
|
||||
"type": "string",
|
||||
"description": "Return tasks due before this date (YYYY-MM-DD). Use today's date to find overdue tasks.",
|
||||
},
|
||||
"due_after": {
|
||||
"type": "string",
|
||||
"description": "Return tasks due on or after this date (YYYY-MM-DD)",
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "Maximum number of tasks to return (default 10)",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
# CalDAV tools — only included when user has CalDAV configured
|
||||
@@ -370,6 +425,47 @@ _CALDAV_TOOLS = [
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "update_todo",
|
||||
"description": "Update a CalDAV todo's summary, due date, description, or priority.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {
|
||||
"type": "string",
|
||||
"description": "Search term to find the todo to update (matches against summary)",
|
||||
},
|
||||
"summary": {
|
||||
"type": "string",
|
||||
"description": "New summary/title for the todo",
|
||||
},
|
||||
"due": {
|
||||
"type": "string",
|
||||
"description": "New due date or datetime in ISO 8601 format",
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "New description",
|
||||
},
|
||||
"priority": {
|
||||
"type": "integer",
|
||||
"description": "New priority (1=highest, 9=lowest)",
|
||||
},
|
||||
"timezone": {
|
||||
"type": "string",
|
||||
"description": "Optional IANA timezone for due datetime",
|
||||
},
|
||||
"calendar_name": {
|
||||
"type": "string",
|
||||
"description": "Optional calendar name to search in",
|
||||
},
|
||||
},
|
||||
"required": ["query"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
@@ -508,6 +604,12 @@ async def execute_tool(user_id: int, tool_name: str, arguments: dict) -> dict:
|
||||
update_fields["body"] = note.body + "\n\n" + new_body
|
||||
else:
|
||||
update_fields["body"] = new_body
|
||||
if "status" in arguments:
|
||||
update_fields["status"] = arguments["status"]
|
||||
if "priority" in arguments:
|
||||
update_fields["priority"] = arguments["priority"]
|
||||
if "due_date" in arguments:
|
||||
update_fields["due_date"] = _parse_due_date(arguments["due_date"])
|
||||
|
||||
updated = await update_note(user_id, note.id, **update_fields)
|
||||
if updated is None:
|
||||
@@ -524,6 +626,38 @@ async def execute_tool(user_id: int, tool_name: str, arguments: dict) -> dict:
|
||||
"suggested_tags": suggested,
|
||||
}
|
||||
|
||||
elif tool_name == "list_tasks":
|
||||
notes, total = await list_notes(
|
||||
user_id=user_id,
|
||||
is_task=True,
|
||||
status=arguments.get("status"),
|
||||
priority=arguments.get("priority"),
|
||||
due_before=_parse_due_date(arguments.get("due_before")),
|
||||
due_after=_parse_due_date(arguments.get("due_after")),
|
||||
limit=int(arguments.get("limit", 10)),
|
||||
sort="due_date",
|
||||
order="asc",
|
||||
)
|
||||
results = []
|
||||
for n in notes:
|
||||
results.append({
|
||||
"id": n.id,
|
||||
"title": n.title,
|
||||
"status": n.status,
|
||||
"priority": n.priority,
|
||||
"due_date": str(n.due_date) if n.due_date else None,
|
||||
"preview": (n.body[:120] + "...") if n.body and len(n.body) > 120 else (n.body or ""),
|
||||
})
|
||||
return {
|
||||
"success": True,
|
||||
"type": "tasks",
|
||||
"data": {
|
||||
"total": total,
|
||||
"count": len(results),
|
||||
"results": results,
|
||||
},
|
||||
}
|
||||
|
||||
elif tool_name == "search_notes":
|
||||
query = arguments.get("query", "")
|
||||
notes, total = await list_notes(user_id=user_id, q=query, limit=5)
|
||||
@@ -671,6 +805,23 @@ async def execute_tool(user_id: int, tool_name: str, arguments: dict) -> dict:
|
||||
},
|
||||
}
|
||||
|
||||
elif tool_name == "update_todo":
|
||||
result = await update_todo(
|
||||
user_id=user_id,
|
||||
query=arguments["query"],
|
||||
summary=arguments.get("summary"),
|
||||
due=arguments.get("due"),
|
||||
description=arguments.get("description"),
|
||||
priority=arguments.get("priority"),
|
||||
timezone=arguments.get("timezone"),
|
||||
calendar_name=arguments.get("calendar_name"),
|
||||
)
|
||||
return {
|
||||
"success": True,
|
||||
"type": "todo_updated",
|
||||
"data": result,
|
||||
}
|
||||
|
||||
elif tool_name == "complete_todo":
|
||||
result = await complete_todo(
|
||||
user_id=user_id,
|
||||
|
||||
Reference in New Issue
Block a user