Remove CalDAV todo tools; overhaul quick-capture

- Remove all 6 CalDAV todo tools (create/list/update/complete/delete/search_todos)
  from tools.py definitions, imports, execute_tool branches, intent routing rules,
  generation_task labels/actions, and llm.py system prompt hints. CalDAV event
  tools remain. Todo functions still exist in caldav.py but are no longer exposed.

- Quick-capture now uses a dedicated classify_capture_intent() with a focused
  _CAPTURE_SYSTEM_PROMPT that always routes to a tool (never null). Tool set
  expanded: create_note/task/event + update_note + research_topic.

- research_topic in quick-capture calls run_research_pipeline() directly (no SSE
  buffer). run_research_pipeline() now accepts buf=None; all buf.append_event
  calls are guarded so status events are skipped when no buffer is provided.

- Fallback note now always sets body=text (was empty for texts ≤80 chars).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-01 19:13:07 -05:00
parent 4c77eab84b
commit 6f5170854b
7 changed files with 122 additions and 318 deletions
+71 -6
View File
@@ -84,15 +84,9 @@ Rules:
- "what are my overdue tasks", "show overdue", "tasks due today", "high priority tasks", "in progress tasks", "what's due this week" → use list_tasks with appropriate status/priority/due_before/due_after filters. For overdue, set due_before to today's date.
- If a note was created earlier in the conversation and the user provides more content for it, use update_note (not create_note).
- Use create_note ONLY when the user explicitly wants a brand new note that doesn't already exist.
- "update", "change", "rename", "set due date on" a CalDAV todo → use update_todo.
- "update", "change", "move", "reschedule" an event → use update_event.
- "delete", "cancel", "remove" an event → use delete_event.
- "which calendars", "list calendars", "my calendars" → use list_calendars.
- "create a calendar todo", "add a CalDAV todo" → use create_todo. Default to create_task for general tasks unless the user explicitly mentions CalDAV or calendar todo.
- "show calendar todos", "list calendar todos" → use list_todos.
- "find calendar todo", "search calendar todos", "find todo named X" → use search_todos with query=<keyword>.
- "completed/finished calendar todo" → use complete_todo.
- "delete/remove calendar todo" → use delete_todo.
- "remind me X minutes/hours before" an event → convert to reminder_minutes parameter on create_event.
- When the user asks about events in a time period (e.g. "events in September", "what's on next week", "my schedule for March"), use list_events with date_from/date_to covering that period. Do NOT use search_events for time-based queries — search_events is only for keyword matching against event titles.
- "delete", "remove", "trash", "get rid of" a note (not a task) → use delete_note with query=<note name/keyword>. NEVER use this for tasks.
@@ -279,3 +273,74 @@ def _try_json(text: str) -> dict | list | None:
return json.loads(text)
except (json.JSONDecodeError, TypeError):
return None
# ── Quick-capture classifier ──────────────────────────────────────────────────
# A stripped-down prompt designed for the /api/quick-capture endpoint.
# Unlike the general intent prompt, this ALWAYS routes to a create tool —
# null is not a valid response.
_CAPTURE_SYSTEM_PROMPT = """\
You are a quick-capture classifier. The user has sent a short snippet of text \
from a mobile app or external client. Classify it as a note, task, or calendar \
event, then extract the relevant fields.
Today's date is {today}.
Available tools:
{tool_summary}
Rules:
- You MUST choose one of the available tools. Never return null.
- create_task: action items, todos, reminders, things to do ("buy milk", "call John", "fix the bug", "remind me to…")
- create_event: appointments, meetings, scheduled occurrences with a date/time ("dentist Friday 2pm", "team meeting next Tuesday")
- update_note: updating, editing, appending to an existing note or task ("add to my shopping list: eggs", "mark buy milk done", "append to my meeting notes", "update my project note")
- research_topic: user wants a comprehensive research note from web sources ("research X", "look up X and make a note", "find everything about X", "compile a note on X")
- create_note: everything else — ideas, observations, links, excerpts, longer text
- For create_task / create_event: extract a concise title; put any extra detail in "body"
- For create_note: use a short descriptive title (≤60 chars); put the FULL original text as "body"
- For update_note: set "query" to the note or task title to find; set other fields as needed
- For research_topic: set "topic" to the subject being researched
- For dates use YYYY-MM-DD; for datetime use ISO 8601
- confidence: "high" if the type is clear; "medium" if you're guessing
Respond with ONLY a JSON object:
{{"tool": "tool_name", "arguments": {{...}}, "confidence": "high"|"medium"}}
Do NOT wrap in markdown code fences."""
async def classify_capture_intent(
text: str,
tools: list[dict],
model: str,
) -> IntentResult:
"""Classify quick-capture text and extract arguments.
Uses a simplified prompt that always routes to a create tool — never null.
Returns IntentResult with tool_name set. Falls back to IntentResult() only
on LLM/parse failure (caller should handle that case).
"""
if not tools:
return IntentResult()
tool_summary = _build_tool_summary(tools)
today = date_type.today().isoformat()
messages = [
{
"role": "system",
"content": _CAPTURE_SYSTEM_PROMPT.format(
today=today, tool_summary=tool_summary
),
},
{"role": "user", "content": text},
]
try:
raw = await generate_completion(messages, model, max_tokens=300, num_ctx=2048)
except Exception:
logger.warning("Quick-capture intent LLM call failed", exc_info=True)
return IntentResult()
return _parse_intent(raw, tools)