111 lines
3.5 KiB
Python
111 lines
3.5 KiB
Python
"""Timeline agent — project milestone management (list, create, update, delete)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from langchain_core.tools import tool
|
|
|
|
from app.core.ws_context import execute_on_client
|
|
|
|
TIMELINE_SYSTEM_PROMPT = (
|
|
"You are a project timeline assistant. Timelines 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 timeline, 0 otherwise\n"
|
|
" - is_approved: 0 until the user explicitly confirms; then 1\n"
|
|
" - For update_timeline, use -1 for integer fields you do not want to change\n"
|
|
" - Listing without a project_id returns all timelines across projects\n"
|
|
" - Always echo the title and formatted date in your confirmation."
|
|
)
|
|
|
|
|
|
@tool
|
|
async def list_timelines(project_id: str = "") -> str:
|
|
"""List timelines. Provide project_id to scope to a specific project."""
|
|
result = await execute_on_client(
|
|
action="select",
|
|
table="timelines",
|
|
filters={"projectId": project_id or None},
|
|
)
|
|
rows = result.get("rows", [])
|
|
if not rows:
|
|
return "No timelines found."
|
|
lines = [f"- {r['title']} (date: {r['date']}, id: {r['id']})" for r in rows]
|
|
return f"Found {len(rows)} timeline(s):\n" + "\n".join(lines)
|
|
|
|
|
|
@tool
|
|
async def create_timeline(
|
|
project_id: str,
|
|
title: str,
|
|
date: int,
|
|
is_ai_suggested: int = 0,
|
|
is_approved: int = 0,
|
|
) -> str:
|
|
"""Create a project timeline (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
|
|
"""
|
|
result = await execute_on_client(
|
|
action="insert",
|
|
table="timelines",
|
|
data={
|
|
"projectId": project_id,
|
|
"title": title,
|
|
"date": date,
|
|
"isAiSuggested": is_ai_suggested,
|
|
"isApproved": is_approved,
|
|
},
|
|
)
|
|
row = result["row"]
|
|
return f"Timeline created: '{row['title']}' (id: {row['id']}, date: {row['date']})"
|
|
|
|
|
|
@tool
|
|
async def update_timeline(
|
|
timeline_id: str,
|
|
title: str = "",
|
|
date: int = -1,
|
|
is_approved: int = -1,
|
|
) -> str:
|
|
"""Update a timeline. Only pass fields that should change.
|
|
timeline_id: UUID of the timeline (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
|
|
result = await execute_on_client(
|
|
action="update",
|
|
table="timelines",
|
|
data={"id": timeline_id, "updates": updates},
|
|
)
|
|
row = result["row"]
|
|
return f"Timeline updated: '{row['title']}' (id: {row['id']})"
|
|
|
|
|
|
@tool
|
|
async def delete_timeline(timeline_id: str) -> str:
|
|
"""Delete a timeline permanently by its UUID."""
|
|
await execute_on_client(action="delete", table="timelines", data={"id": timeline_id})
|
|
return f"Timeline {timeline_id} deleted."
|
|
|
|
|
|
TIMELINE_TOOLS: list[Any] = [
|
|
list_timelines,
|
|
create_timeline,
|
|
update_timeline,
|
|
delete_timeline,
|
|
]
|