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
+12 -7
View File
@@ -26,15 +26,16 @@ async def run_research_pipeline(
user_id: int,
model: str,
intent_model: str,
buf,
buf=None,
) -> Note:
"""Full research pipeline: search → fetch → synthesize → create note.
Emits status events via buf.append_event throughout.
Emits status events via buf.append_event throughout (when buf is provided).
Returns the created Note.
"""
# Step 1: Generate sub-queries
buf.append_event("status", {"status": "Generating search queries..."})
if buf is not None:
buf.append_event("status", {"status": "Generating search queries..."})
queries = await _generate_sub_queries(topic, intent_model)
logger.info("Research: generated %d sub-queries for topic '%s'", len(queries), topic)
@@ -42,7 +43,8 @@ async def run_research_pipeline(
async def _search_with_stagger(i: int, query: str) -> tuple[str, list[dict]]:
if i > 0:
await asyncio.sleep(0.2 * i)
buf.append_event("status", {"status": f"Searching: {query}..."})
if buf is not None:
buf.append_event("status", {"status": f"Searching: {query}..."})
results = await _search_searxng(query)
logger.info("Research: query '%s'%d results", query, len(results))
return query, results
@@ -64,7 +66,8 @@ async def run_research_pipeline(
# Fetch all unique URLs in parallel
async def _fetch_source(url: str, result: dict, query: str) -> dict:
title = result.get("title", url)
buf.append_event("status", {"status": f"Reading: {title[:60]}..."})
if buf is not None:
buf.append_event("status", {"status": f"Reading: {title[:60]}..."})
content = await fetch_url_content(url)
return {
"url": url,
@@ -98,11 +101,13 @@ async def run_research_pipeline(
)
# Step 4: Synthesize (streams tokens into chat as the note is being written)
buf.append_event("status", {"status": f"Synthesizing report from {len(synthesis_sources)} sources..."})
if buf is not None:
buf.append_event("status", {"status": f"Synthesizing report from {len(synthesis_sources)} sources..."})
title, body = await _synthesize_note(topic, synthesis_sources, model, buf)
# Step 5: Create note
buf.append_event("status", {"status": "Saving note..."})
if buf is not None:
buf.append_event("status", {"status": "Saving note..."})
note = await create_note(
user_id=user_id,
title=title,