"""Checkpoint agent — project milestone management (list, create, update, delete).""" from __future__ import annotations import json from typing import Any from langchain_core.messages import HumanMessage, SystemMessage from langchain_core.tools import tool from app.core.agent_registry import ChatAgent, registry from app.core.llm import get_llm _SYSTEM_PROMPT = ( "You are a project checkpoint assistant. Checkpoints are milestone dates that\n" "track progress on a project — they are not calendar events.\n\n" "Rules:\n" " - project_id is REQUIRED for every create; confirm with the user if unknown\n" " - date is a Unix timestamp in milliseconds; convert human-readable dates\n" " - is_ai_suggested: 1 when proactively proposing a checkpoint, 0 otherwise\n" " - is_approved: 0 until the user explicitly confirms; then 1\n" " - For update_checkpoint, use -1 for integer fields you do not want to change\n" " - Listing without a project_id returns all checkpoints across projects\n" " - Always echo the title and formatted date in your confirmation." ) @tool async def list_checkpoints(project_id: str = "") -> str: """List checkpoints. Provide project_id to scope to a specific project.""" return json.dumps({ "action": "list", "table": "checkpoints", "filters": {"projectId": project_id or None}, }) @tool async def create_checkpoint( project_id: str, title: str, date: int, is_ai_suggested: int = 0, is_approved: int = 0, ) -> str: """Create a project checkpoint (milestone). project_id: REQUIRED UUID of the parent project title: descriptive name for the milestone date: Unix timestamp in milliseconds is_ai_suggested: 1 if proactively suggested, 0 if user-requested is_approved: 0 until the user confirms """ return json.dumps({ "action": "create_record", "table": "checkpoints", "data": { "projectId": project_id, "title": title, "date": date, "isAiSuggested": is_ai_suggested, "isApproved": is_approved, }, }) @tool async def update_checkpoint( checkpoint_id: str, title: str = "", date: int = -1, is_approved: int = -1, ) -> str: """Update a checkpoint. Only pass fields that should change. checkpoint_id: UUID of the checkpoint (required) date: -1 means unchanged; any other value sets the new date (ms timestamp) is_approved: -1 means unchanged; 0 or 1 sets the approval state """ updates: dict[str, Any] = {} if title: updates["title"] = title if date != -1: updates["date"] = date if is_approved != -1: updates["isApproved"] = is_approved return json.dumps({ "action": "update_record", "table": "checkpoints", "data": {"id": checkpoint_id, "updates": updates}, }) @tool async def delete_checkpoint(checkpoint_id: str) -> str: """Delete a checkpoint permanently by its UUID.""" return json.dumps({ "action": "delete_record", "table": "checkpoints", "data": {"id": checkpoint_id}, }) @registry.register class CheckpointAgent(ChatAgent): def get_name(self) -> str: return "checkpoint_agent" def get_description(self) -> str: return "Manages project checkpoints (milestones): list, create, update, delete" def get_tools(self) -> list[Any]: return [list_checkpoints, create_checkpoint, update_checkpoint, delete_checkpoint] async def handle(self, query: str, context: dict[str, Any]) -> str: llm = get_llm() messages = [ SystemMessage(content=_SYSTEM_PROMPT), HumanMessage( content=f"User query: {query}\nContext: {json.dumps(context)[:1000]}" ), ] return await self._tool_loop(llm, messages, self.get_tools())