feat: complete Phase 1 MVP - add agent executor and WebSocket real-time updates
Deploy Dashboard (Dev) / deploy-dev (push) Has been cancelled
Run Tests / Backend Tests (push) Has been cancelled
Run Tests / Frontend Tests (push) Has been cancelled
Run Tests / Test Summary (push) Has been cancelled

Agent Executor Service:
- BaseAgent class with LLM integration via LiteLLM proxy
- AgentExecutor service that processes pending executions every 5 seconds
- Action execution: create_subtask, update_status, send_notification, add_comment
- Approval workflow: CxO level agents require user approval before execution
- Telegram notifications for approvals and completions
- Error handling and execution status tracking

WebSocket Real-Time Updates:
- WebSocket endpoint at /ws/opc for real-time task updates
- ConnectionManager for broadcasting to all connected clients
- Frontend WebSocket client with auto-reconnect
- Live updates for task create/update/move/delete actions
- Agent execution status broadcasts

Integration:
- Agent executor starts on dashboard startup
- Task router broadcasts WebSocket updates on all mutations
- Frontend OPC page subscribes to WebSocket and updates UI in real-time
- Agents (PM, CTO, COO) can now execute tasks autonomously

Phase 1 MVP Complete:
 PostgreSQL database with full schema
 Task CRUD API with automatic time tracking
 Kanban board with drag-and-drop
 Agent executor service with LLM integration
 WebSocket real-time updates
 3 core agents ready to execute

Next: Agent panel UI, email notifications, PDF invoicing
This commit is contained in:
Gan, Jimmy
2026-03-31 14:46:44 +08:00
parent fc0bb2979b
commit 1422cc9bc8
8 changed files with 720 additions and 2 deletions
+12
View File
@@ -7,6 +7,7 @@ from pydantic import BaseModel
from datetime import datetime
from db import opc_db
from rbac import require_page, User, _inject_user
from routers import opc_ws
router = APIRouter()
@@ -97,6 +98,9 @@ async def create_task(
actor=user.username
)
# Broadcast WebSocket update
await opc_ws.broadcast_task_update(created_task["id"], "created", created_task)
return created_task
@@ -153,6 +157,10 @@ async def update_task(
updates=updates,
actor=user.username
)
# Broadcast WebSocket update
await opc_ws.broadcast_task_update(task_id, "updated", updated_task)
return updated_task
@@ -163,6 +171,10 @@ async def delete_task(
):
"""Delete a task"""
await opc_db.delete_task(task_id=task_id, actor=user.username)
# Broadcast WebSocket update
await opc_ws.broadcast_task_update(task_id, "deleted", {"id": task_id})
return {"ok": True}
+107
View File
@@ -0,0 +1,107 @@
"""
OPC WebSocket Router - Real-time updates for tasks and agent executions
"""
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Depends
from typing import Set
import json
import asyncio
import logging
from auth import get_current_user_ws
logger = logging.getLogger(__name__)
router = APIRouter()
# Active WebSocket connections
active_connections: Set[WebSocket] = set()
class ConnectionManager:
"""Manages WebSocket connections"""
def __init__(self):
self.active_connections: Set[WebSocket] = set()
async def connect(self, websocket: WebSocket):
"""Accept and register a new connection"""
await websocket.accept()
self.active_connections.add(websocket)
logger.info(f"WebSocket connected. Total connections: {len(self.active_connections)}")
def disconnect(self, websocket: WebSocket):
"""Remove a connection"""
self.active_connections.discard(websocket)
logger.info(f"WebSocket disconnected. Total connections: {len(self.active_connections)}")
async def broadcast(self, message: dict):
"""Broadcast message to all connected clients"""
disconnected = set()
for connection in self.active_connections:
try:
await connection.send_json(message)
except Exception as e:
logger.error(f"Failed to send message: {e}")
disconnected.add(connection)
# Clean up disconnected clients
for conn in disconnected:
self.disconnect(conn)
manager = ConnectionManager()
@router.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
"""WebSocket endpoint for OPC real-time updates"""
await manager.connect(websocket)
try:
while True:
# Keep connection alive and receive any client messages
data = await websocket.receive_text()
# Echo back for ping/pong
if data == "ping":
await websocket.send_text("pong")
except WebSocketDisconnect:
manager.disconnect(websocket)
except Exception as e:
logger.error(f"WebSocket error: {e}")
manager.disconnect(websocket)
async def broadcast_task_update(task_id: int, action: str, task_data: dict):
"""Broadcast task update to all connected clients"""
message = {
"type": "task_update",
"task_id": task_id,
"action": action, # created, updated, moved, deleted
"data": task_data,
"timestamp": task_data.get("updated_at")
}
await manager.broadcast(message)
async def broadcast_agent_execution(execution_id: int, status: str, execution_data: dict):
"""Broadcast agent execution update"""
message = {
"type": "agent_execution",
"execution_id": execution_id,
"status": status, # pending, running, completed, failed, pending_approval
"data": execution_data,
"timestamp": execution_data.get("started_at") or execution_data.get("completed_at")
}
await manager.broadcast(message)
async def broadcast_agent_status(agent_id: str, status: str, message_text: str = ""):
"""Broadcast agent status update"""
message = {
"type": "agent_status",
"agent_id": agent_id,
"status": status, # idle, working, error
"message": message_text
}
await manager.broadcast(message)