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:
2026-02-17 23:22:02 -05:00
parent 70cba72a80
commit 4df5ec2d65
5 changed files with 284 additions and 13 deletions
+40 -1
View File
@@ -19,6 +19,10 @@ const label = computed(() => {
return "Created note";
case "note_updated":
return "Updated note";
case "tasks":
return "Tasks";
case "todo_updated":
return "Updated todo";
case "search":
return "Searched notes";
case "event":
@@ -95,7 +99,7 @@ const calendarList = computed(() => {
const todoData = computed(() => {
const data = props.toolCall.result.data;
const type = props.toolCall.result.type;
if (!data || (type !== "todo" && type !== "todo_completed" && type !== "todo_deleted")) return null;
if (!data || (type !== "todo" && type !== "todo_completed" && type !== "todo_deleted" && type !== "todo_updated")) return null;
return data as { summary: string; due?: string; status?: string };
});
@@ -141,6 +145,19 @@ function formatEventTime(iso: string): string {
}
}
const taskList = computed(() => {
const data = props.toolCall.result.data;
if (!data || props.toolCall.result.type !== "tasks") return null;
const results = data.results as Array<{ id: number; title: string; status: string; priority: string; due_date?: string }> | undefined;
return results ?? [];
});
const taskCount = computed(() => {
const data = props.toolCall.result.data;
if (!data || props.toolCall.result.type !== "tasks") return 0;
return (data.total as number) ?? 0;
});
const searchResults = computed(() => {
const data = props.toolCall.result.data;
if (!data || props.toolCall.result.type !== "search") return null;
@@ -215,6 +232,17 @@ async function applyTag(tag: string) {
</div>
</div>
</template>
<template v-else-if="taskList !== null">
<span class="tool-search-info">{{ taskCount }} found</span>
<div v-if="taskList.length > 0" class="tool-event-list">
<div v-for="(t, i) in taskList.slice(0, 5)" :key="i" class="tool-event-item">
<router-link :to="`/tasks/${t.id}`" class="tool-event-item-title">{{ t.title }}</router-link>
<span v-if="t.due_date" class="tool-event-item-time">{{ t.due_date }}</span>
<span v-if="t.priority && t.priority !== 'none'" class="tool-task-priority" :class="`priority-${t.priority}`">{{ t.priority }}</span>
</div>
<div v-if="taskList.length > 5" class="tool-event-more">+{{ taskList.length - 5 }} more</div>
</div>
</template>
<template v-else-if="eventList !== null">
<span class="tool-search-info">{{ eventCount }} found</span>
<div v-if="eventList.length > 0" class="tool-event-list">
@@ -341,6 +369,17 @@ async function applyTag(tag: string) {
text-decoration: line-through;
opacity: 0.7;
}
.tool-task-priority {
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
padding: 0.1rem 0.3rem;
border-radius: 3px;
}
.priority-high { color: var(--color-danger, #e74c3c); }
.priority-medium { color: var(--color-warning, #f59e0b); }
.priority-low { color: var(--color-text-muted); }
.tool-completed {
text-decoration: line-through;
color: var(--color-success, #2ecc71);