rename from checkpoint to timeline agent
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
"""Import all agent modules to trigger @registry.register decorators."""
|
||||
|
||||
from app.agents import checkpoint_agent, note_agent, project_agent, task_agent
|
||||
from app.agents import timeline_agent, note_agent, project_agent, task_agent
|
||||
|
||||
__all__ = ["checkpoint_agent", "note_agent", "project_agent", "task_agent"]
|
||||
__all__ = ["timeline_agent", "note_agent", "project_agent", "task_agent"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Checkpoint agent — project milestone management (list, create, update, delete)."""
|
||||
"""Timeline agent — project milestone management (list, create, update, delete)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -13,43 +13,43 @@ from app.core.llm import get_llm
|
||||
from app.core.ws_context import execute_on_client
|
||||
|
||||
_SYSTEM_PROMPT = (
|
||||
"You are a project checkpoint assistant. Checkpoints are milestone dates that\n"
|
||||
"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 checkpoint, 0 otherwise\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_checkpoint, use -1 for integer fields you do not want to change\n"
|
||||
" - Listing without a project_id returns all checkpoints across projects\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_checkpoints(project_id: str = "") -> str:
|
||||
"""List checkpoints. Provide project_id to scope to a specific project."""
|
||||
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="checkpoints",
|
||||
table="timelines",
|
||||
filters={"projectId": project_id or None},
|
||||
)
|
||||
rows = result.get("rows", [])
|
||||
if not rows:
|
||||
return "No checkpoints found."
|
||||
return "No timelines found."
|
||||
lines = [f"- {r['title']} (date: {r['date']}, id: {r['id']})" for r in rows]
|
||||
return f"Found {len(rows)} checkpoint(s):\n" + "\n".join(lines)
|
||||
return f"Found {len(rows)} timeline(s):\n" + "\n".join(lines)
|
||||
|
||||
|
||||
@tool
|
||||
async def create_checkpoint(
|
||||
async def create_timeline(
|
||||
project_id: str,
|
||||
title: str,
|
||||
date: int,
|
||||
is_ai_suggested: int = 0,
|
||||
is_approved: int = 0,
|
||||
) -> str:
|
||||
"""Create a project checkpoint (milestone).
|
||||
"""Create a project timeline (milestone).
|
||||
project_id: REQUIRED UUID of the parent project
|
||||
title: descriptive name for the milestone
|
||||
date: Unix timestamp in milliseconds
|
||||
@@ -58,7 +58,7 @@ async def create_checkpoint(
|
||||
"""
|
||||
result = await execute_on_client(
|
||||
action="insert",
|
||||
table="checkpoints",
|
||||
table="timelines",
|
||||
data={
|
||||
"projectId": project_id,
|
||||
"title": title,
|
||||
@@ -68,18 +68,18 @@ async def create_checkpoint(
|
||||
},
|
||||
)
|
||||
row = result["row"]
|
||||
return f"Checkpoint created: '{row['title']}' (id: {row['id']}, date: {row['date']})"
|
||||
return f"Timeline created: '{row['title']}' (id: {row['id']}, date: {row['date']})"
|
||||
|
||||
|
||||
@tool
|
||||
async def update_checkpoint(
|
||||
checkpoint_id: str,
|
||||
async def update_timeline(
|
||||
timeline_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)
|
||||
"""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
|
||||
"""
|
||||
@@ -92,30 +92,30 @@ async def update_checkpoint(
|
||||
updates["isApproved"] = is_approved
|
||||
result = await execute_on_client(
|
||||
action="update",
|
||||
table="checkpoints",
|
||||
data={"id": checkpoint_id, "updates": updates},
|
||||
table="timelines",
|
||||
data={"id": timeline_id, "updates": updates},
|
||||
)
|
||||
row = result["row"]
|
||||
return f"Checkpoint updated: '{row['title']}' (id: {row['id']})"
|
||||
return f"Timeline updated: '{row['title']}' (id: {row['id']})"
|
||||
|
||||
|
||||
@tool
|
||||
async def delete_checkpoint(checkpoint_id: str) -> str:
|
||||
"""Delete a checkpoint permanently by its UUID."""
|
||||
await execute_on_client(action="delete", table="checkpoints", data={"id": checkpoint_id})
|
||||
return f"Checkpoint {checkpoint_id} deleted."
|
||||
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."
|
||||
|
||||
|
||||
@registry.register
|
||||
class CheckpointAgent(ChatAgent):
|
||||
class TimelineAgent(ChatAgent):
|
||||
def get_name(self) -> str:
|
||||
return "checkpoint_agent"
|
||||
return "timeline_agent"
|
||||
|
||||
def get_description(self) -> str:
|
||||
return "Manages project checkpoints (milestones): list, create, update, delete"
|
||||
return "Manages project timelines (milestones): list, create, update, delete"
|
||||
|
||||
def get_tools(self) -> list[Any]:
|
||||
return [list_checkpoints, create_checkpoint, update_checkpoint, delete_checkpoint]
|
||||
return [list_timelines, create_timeline, update_timeline, delete_timeline]
|
||||
|
||||
async def handle(self, query: str, context: dict[str, Any]) -> str:
|
||||
llm = get_llm()
|
||||
@@ -107,7 +107,7 @@ and produce a detailed prompt_template that a separate AI will use as its instru
|
||||
|
||||
Ask concise, focused questions one at a time. Cover these topics (not necessarily in this order):
|
||||
1. The type and format of the source content.
|
||||
2. Which data types to extract: tasks, notes, checkpoints, and/or projects.
|
||||
2. Which data types to extract: tasks, notes, timelines, and/or projects.
|
||||
3. How fields should be mapped (e.g. email subject → task title).
|
||||
4. Priority or status rules (e.g. "urgent" keyword → high priority).
|
||||
5. Any special handling, date extraction, or exclusions.
|
||||
@@ -121,7 +121,7 @@ these exact markers on their own lines:
|
||||
|
||||
The prompt_template must be a self-contained instruction for an AI that receives a document/email/message \
|
||||
and must return a JSON array of records in this shape:
|
||||
[{{ "table": "<tasks|notes|checkpoints|projects>", "data": {{ <field: value> }} }}, ...]
|
||||
[{{ "table": "<tasks|notes|timelines|projects>", "data": {{ <field: value> }} }}, ...]
|
||||
|
||||
Rules for the generated template:
|
||||
- Be explicit about field names (camelCase: title, status, priority, dueDate, projectId, content, etc.).
|
||||
|
||||
@@ -53,7 +53,7 @@ _INSERT_TIMEOUT: int = 30
|
||||
# ── Allowed tables & extraction schema hints ───────────────────────────────
|
||||
|
||||
_ALLOWED_TABLES: frozenset[str] = frozenset(
|
||||
{"tasks", "notes", "checkpoints", "projects", "taskComments"}
|
||||
{"tasks", "notes", "timelines", "projects", "taskComments"}
|
||||
)
|
||||
|
||||
# Field descriptions fed to the extraction LLM as concise schema references.
|
||||
@@ -65,7 +65,7 @@ _TABLE_SCHEMAS: dict[str, str] = {
|
||||
"assignee (JSON array string), dueDate (ms timestamp int), projectId (str)"
|
||||
),
|
||||
"notes": "title (str, required), content (str, markdown), projectId (str)",
|
||||
"checkpoints": (
|
||||
"timelines": (
|
||||
"title (str, required), projectId (str, required), date (ms timestamp int)"
|
||||
),
|
||||
"projects": "name (str, required), clientId (str)",
|
||||
|
||||
@@ -159,9 +159,9 @@ def _register_builtin_templates() -> None:
|
||||
"list, and track tasks. Use correct status values (todo, in_progress, "
|
||||
"done) and priority values (high, medium, low) from the workspace model."
|
||||
),
|
||||
"tpl_checkpoint_agent_default": (
|
||||
"You are a project checkpoint assistant. Help the user create and manage "
|
||||
"milestone checkpoints on their projects. Every checkpoint requires a "
|
||||
"tpl_timeline_agent_default": (
|
||||
"You are a project timeline assistant. Help the user create and manage "
|
||||
"milestone timelines on their projects. Every timeline requires a "
|
||||
"project_id and a date expressed as a Unix timestamp in milliseconds."
|
||||
),
|
||||
"tpl_project_agent_default": (
|
||||
@@ -182,7 +182,7 @@ def _register_builtin_templates() -> None:
|
||||
"tpl_note_weekly_summary": (
|
||||
"Generate a weekly project summary note from the provided workspace data. "
|
||||
"Include: tasks completed this week, tasks due soon, active projects, "
|
||||
"and upcoming checkpoints. Format the output as clean Markdown."
|
||||
"and upcoming timelines. Format the output as clean Markdown."
|
||||
),
|
||||
}
|
||||
for tid, text in _tpls.items():
|
||||
|
||||
@@ -27,7 +27,7 @@ _VALID_CHART_TYPES = {"area", "bar", "line", "pie", "radar", "radial"}
|
||||
# Map agent name → floating domain
|
||||
_AGENT_DOMAIN: dict[str, str] = {
|
||||
"task_agent": "tasks",
|
||||
"checkpoint_agent": "checkpoints",
|
||||
"timeline_agent": "timelines",
|
||||
"note_agent": "notes",
|
||||
"project_agent": "projects",
|
||||
}
|
||||
@@ -171,8 +171,8 @@ class HomeFormatter:
|
||||
)
|
||||
|
||||
if block_type == "timeline":
|
||||
if not isinstance(obj.get("checkpoints"), list):
|
||||
logger.warning("HomeFormatter: timeline missing checkpoints — skipping")
|
||||
if not isinstance(obj.get("timelines"), list):
|
||||
logger.warning("HomeFormatter: timeline missing timelines — skipping")
|
||||
return None
|
||||
return WsStreamBlock(
|
||||
request_id=self.request_id,
|
||||
|
||||
@@ -29,8 +29,8 @@ ALLOWED_PERMISSIONS: frozenset[str] = frozenset(
|
||||
"write:projects",
|
||||
"read:notes",
|
||||
"write:notes",
|
||||
"read:checkpoints",
|
||||
"write:checkpoints",
|
||||
"read:timelines",
|
||||
"write:timelines",
|
||||
"read:calendar",
|
||||
"write:calendar",
|
||||
}
|
||||
|
||||
@@ -268,7 +268,7 @@ class WsAgentComplete(BaseModel):
|
||||
class WsFloatingScope(BaseModel):
|
||||
"""Scope for a floating request — narrows the agent to a specific entity."""
|
||||
|
||||
type: Literal["task", "project", "note", "checkpoint"]
|
||||
type: Literal["task", "project", "note", "timeline"]
|
||||
id: str | None = None
|
||||
|
||||
|
||||
@@ -325,7 +325,7 @@ class WsFloatingDomain(BaseModel):
|
||||
|
||||
type: Literal[WsFrameType.floating_domain] = WsFrameType.floating_domain
|
||||
request_id: str
|
||||
domain: Literal["tasks", "checkpoints", "notes", "projects"]
|
||||
domain: Literal["tasks", "timelines", "notes", "projects"]
|
||||
|
||||
|
||||
# ── Agent Catalog ─────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user