"""Project agent — full lifecycle management (list, get, create, update, archive, delete). Adapted for Chat Service: import from app.ws_context instead of app.core.ws_context. """ from __future__ import annotations from typing import Any from langchain_core.tools import tool from app.ws_context import execute_on_client PROJECT_SYSTEM_PROMPT = ( "You are a project management assistant. You help users create, find,\n" "update, and archive projects in their workspace.\n\n" "Rules:\n" " - status must be one of: active, archived\n" " - client_id is optional; link to a client only when explicitly mentioned\n" " - ai_summary is populated only when the user asks for a project summary;\n" " derive it from context data — do not fabricate content\n" " - Use list_projects for scoped queries; list_all_projects only when the\n" " user wants a complete cross-client view including archived projects\n" " - get_project requires a project UUID; resolve the ID first by calling\n" " list_projects if you only have a project name\n" " - Prefer archiving (update_project status=archived) over deletion;\n" " only call delete_project when the user explicitly confirms deletion." ) @tool async def list_projects( client_id: str = "", include_archived: int = 0, ) -> str: """List projects, optionally filtered by client_id. include_archived: 1 to include archived projects, 0 for active only (default). """ result = await execute_on_client( action="select", table="projects", filters={ "clientId": client_id or None, "includeArchived": bool(include_archived), }, ) rows = result.get("rows", []) if not rows: return "No projects found." lines = [f"- {r['name']} (status: {r['status']}, id: {r['id']})" for r in rows] return f"Found {len(rows)} project(s):\n" + "\n".join(lines) @tool async def list_all_projects() -> str: """List every project regardless of client or status. Use only when the user wants a complete cross-client overview. """ result = await execute_on_client(action="select", table="projects") rows = result.get("rows", []) if not rows: return "No projects found." lines = [f"- {r['name']} (status: {r['status']}, id: {r['id']})" for r in rows] return f"All projects ({len(rows)}):\n" + "\n".join(lines) @tool async def get_project(project_id: str) -> str: """Fetch a single project by its UUID.""" result = await execute_on_client(action="get", table="projects", data={"id": project_id}) row = result.get("row") if not row: return f"Project {project_id} not found." return ( f"Project: '{row['name']}' (id: {row['id']}, status: {row['status']}, " f"clientId: {row.get('clientId', 'none')})" ) @tool async def create_project( name: str, client_id: str = "", ) -> str: """Create a new project. name: human-readable project name (required) client_id: optional UUID of the owning client """ result = await execute_on_client( action="insert", table="projects", data={"name": name, "clientId": client_id or None}, ) row = result["row"] return f"Project created: '{row['name']}' (id: {row['id']})" @tool async def update_project( project_id: str, name: str = "", client_id: str = "", status: str = "", ai_summary: str = "", ) -> str: """Update a project. Only pass fields that should change. project_id: UUID of the project (required) status: active | archived ai_summary: AI-generated summary text (populate only when explicitly requested) """ updates: dict[str, Any] = {} if name: updates["name"] = name if client_id: updates["clientId"] = client_id if status: updates["status"] = status if ai_summary: updates["aiSummary"] = ai_summary result = await execute_on_client( action="update", table="projects", data={"id": project_id, "updates": updates}, ) row = result["row"] return f"Project updated: '{row['name']}' (id: {row['id']}, status: {row['status']})" @tool async def delete_project(project_id: str) -> str: """Permanently delete a project and orphan its tasks. IMPORTANT: prefer update_project(status='archived') unless the user has explicitly confirmed they want permanent deletion. """ await execute_on_client(action="delete", table="projects", data={"id": project_id}) return f"Project {project_id} permanently deleted." PROJECT_TOOLS: list[Any] = [ list_projects, list_all_projects, get_project, create_project, update_project, delete_project, ]