From beb57876fb66a7e4037cca13b0e8e7df6b00beeb Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Thu, 12 Mar 2026 17:58:13 -0400 Subject: [PATCH] feat: update_milestone tool and workspace milestone refresh - Add update_milestone LLM tool: accepts project + milestone title to look up, then applies any of title/description/status changes - WorkspaceView SSE watcher now triggers taskPanelRef.reload() on create_milestone and update_milestone success, so milestone groups appear immediately without a manual refresh Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/views/WorkspaceView.vue | 2 +- src/fabledassistant/services/tools.py | 47 +++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/frontend/src/views/WorkspaceView.vue b/frontend/src/views/WorkspaceView.vue index bf05ec0..106ad41 100644 --- a/frontend/src/views/WorkspaceView.vue +++ b/frontend/src/views/WorkspaceView.vue @@ -80,7 +80,7 @@ watch( activeNoteId.value = tc.result.data.id as number; } if ( - ["create_task", "update_task"].includes(tc.function) && + ["create_task", "update_task", "create_milestone", "update_milestone"].includes(tc.function) && tc.status === "success" ) { taskPanelRef.value?.reload(); diff --git a/src/fabledassistant/services/tools.py b/src/fabledassistant/services/tools.py index 97efdd5..1013e0d 100644 --- a/src/fabledassistant/services/tools.py +++ b/src/fabledassistant/services/tools.py @@ -436,6 +436,24 @@ _CORE_TOOLS = [ }, }, }, + { + "type": "function", + "function": { + "name": "update_milestone", + "description": "Update the title, description, or status of an existing milestone.", + "parameters": { + "type": "object", + "properties": { + "project": {"type": "string", "description": "Project title the milestone belongs to"}, + "milestone": {"type": "string", "description": "Current milestone title to look up"}, + "title": {"type": "string", "description": "New title (omit to keep current)"}, + "description": {"type": "string", "description": "New description (omit to keep current)"}, + "status": {"type": "string", "enum": ["active", "completed", "cancelled"], "description": "New status (omit to keep current)"}, + }, + "required": ["project", "milestone"], + }, + }, + }, { "type": "function", "function": { @@ -1426,6 +1444,35 @@ async def execute_tool(user_id: int, tool_name: str, arguments: dict) -> dict: ) return {"success": True, "type": "milestone", "data": ms.to_dict()} + elif tool_name == "update_milestone": + from fabledassistant.services.projects import get_project_by_title as _gpbt, list_projects as _lp + from fabledassistant.services.milestones import get_milestone_by_title as _gmbt, update_milestone as _um + project_name = arguments.get("project", "") + milestone_name = arguments.get("milestone", "") + if not project_name or not milestone_name: + return {"success": False, "error": "Both project and milestone are required"} + proj = await _gpbt(user_id, project_name) + if proj is None: + all_p = await _lp(user_id) + matches = [p for p in all_p if project_name.lower() in p.title.lower()] + proj = matches[0] if matches else None + if proj is None: + return {"success": False, "error": f"Project '{project_name}' not found"} + ms = await _gmbt(user_id, proj.id, milestone_name) + if ms is None: + return {"success": False, "error": f"Milestone '{milestone_name}' not found in project '{proj.title}'. Use list_milestones to see available milestones."} + fields: dict[str, object] = {} + if "title" in arguments: + fields["title"] = arguments["title"] + if "description" in arguments: + fields["description"] = arguments["description"] + if "status" in arguments: + fields["status"] = arguments["status"] + if not fields: + return {"success": False, "error": "No fields to update — provide at least one of: title, description, status"} + updated = await _um(user_id, ms.id, **fields) + return {"success": True, "type": "milestone", "data": updated.to_dict()} + elif tool_name == "list_milestones": from fabledassistant.services.projects import get_project_by_title as _gpbt, list_projects as _lp from fabledassistant.services.milestones import get_project_milestone_summary