77339d5c58
Merge create_task into create_note (set status='todo' for tasks, omit for notes), merge delete_task into delete_note, consolidate entity tools (create/update_person → save_person, create/update_place → save_place), rename get_note → read_note with clearer descriptions, move calculate out of rag.py into utility.py, and extract shared duplicate detection into check_duplicate() helper. Updates all downstream references in generation_task.py, quick_capture.py, ToolCallCard.vue, and WorkspaceView.vue. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
117 lines
5.0 KiB
Python
117 lines
5.0 KiB
Python
"""Task tools: list, log work."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from fabledassistant.services.notes import get_note_by_title, list_notes
|
|
from fabledassistant.services.tools._helpers import (
|
|
parse_due_date,
|
|
resolve_project,
|
|
)
|
|
from fabledassistant.services.tools._registry import tool
|
|
|
|
|
|
@tool(
|
|
name="list_tasks",
|
|
description="List tasks with optional filters. For overdue tasks, set due_before to today's date.",
|
|
parameters={
|
|
"q": {"type": "string", "description": "Optional keyword filter — searches title and body"},
|
|
"status": {"type": "array", "items": {"type": "string", "enum": ["todo", "in_progress", "done", "cancelled"]}, "description": "Filter by one or more statuses. Omit for all."},
|
|
"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)"},
|
|
"project": {"type": "string", "description": "Filter tasks by project name"},
|
|
"milestone": {"type": "string", "description": "Filter tasks by milestone name within a project"},
|
|
"limit": {"type": "integer", "description": "Maximum number of tasks to return (default 10)"},
|
|
},
|
|
read_only=True,
|
|
briefing=True,
|
|
)
|
|
async def list_tasks_tool(*, user_id, arguments, **_ctx):
|
|
project_id = None
|
|
milestone_id = None
|
|
project_name = arguments.get("project")
|
|
milestone_name = arguments.get("milestone")
|
|
if project_name:
|
|
proj = await resolve_project(user_id, project_name)
|
|
if proj:
|
|
project_id = proj.id
|
|
if milestone_name:
|
|
from fabledassistant.services.milestones import get_milestone_by_title
|
|
ms = await get_milestone_by_title(user_id, proj.id, milestone_name)
|
|
if ms:
|
|
milestone_id = ms.id
|
|
elif milestone_name:
|
|
from fabledassistant.services.milestones import find_milestone_by_title
|
|
ms = await find_milestone_by_title(user_id, milestone_name)
|
|
if ms:
|
|
milestone_id = ms.id
|
|
notes, total = await list_notes(
|
|
user_id=user_id,
|
|
q=arguments.get("q") or None,
|
|
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")),
|
|
project_id=project_id,
|
|
milestone_id=milestone_id,
|
|
exclude_paused_projects=project_id is None,
|
|
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,
|
|
"project_id": n.project_id,
|
|
"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},
|
|
}
|
|
|
|
|
|
@tool(
|
|
name="log_work",
|
|
description="Add a work log entry to a task to record progress, work done, or time spent. Use this when the user says they worked on, completed, or spent time on a task.",
|
|
parameters={
|
|
"task": {"type": "string", "description": "Title or keyword identifying the task (required)"},
|
|
"content": {"type": "string", "description": "Description of the work done (required)"},
|
|
"duration_minutes": {"type": "integer", "description": "Optional time spent in minutes"},
|
|
},
|
|
required=["task", "content"],
|
|
)
|
|
async def log_work_tool(*, user_id, arguments, **_ctx):
|
|
from fabledassistant.services.task_logs import create_log as _create_log
|
|
|
|
task_query = arguments.get("task", "")
|
|
content = arguments.get("content", "").strip()
|
|
if not task_query:
|
|
return {"success": False, "error": "task is required"}
|
|
if not content:
|
|
return {"success": False, "error": "content is required"}
|
|
duration_minutes = arguments.get("duration_minutes")
|
|
if duration_minutes is not None:
|
|
try:
|
|
duration_minutes = int(duration_minutes)
|
|
except (TypeError, ValueError):
|
|
duration_minutes = None
|
|
note = await get_note_by_title(user_id, task_query)
|
|
if note is None or note.status is None:
|
|
notes, _ = await list_notes(user_id=user_id, q=task_query, is_task=True, limit=5)
|
|
note = notes[0] if notes else None
|
|
if note is None:
|
|
return {"success": False, "error": f"No task found matching '{task_query}'."}
|
|
try:
|
|
log = await _create_log(user_id, note.id, content, duration_minutes)
|
|
except ValueError as e:
|
|
return {"success": False, "error": str(e)}
|
|
return {"success": True, "log": log.to_dict(), "task": note.title}
|