rename from checkpoint to timeline agent

This commit is contained in:
2026-03-10 23:17:38 +01:00
parent f6ed383b3a
commit 2de67213f8
19 changed files with 136 additions and 136 deletions

View File

@@ -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"]

View File

@@ -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()

View File

@@ -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.).

View File

@@ -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)",

View File

@@ -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():

View File

@@ -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,

View File

@@ -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",
}

View File

@@ -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 ─────────────────────────────────────────────────────