Skip to content

Commit cf5727f

Browse files
committed
Refactor backend API and introduce cron scheduling support
- Removed the `teams` endpoint and associated logic, simplifying the agent management API. - Updated agent creation and listing to support grouping without team context. - Added a new `CronScheduler` service to handle scheduled events based on subscription cron expressions. - Enhanced event handling with manual event triggering and event catalog retrieval. - Updated FastAPI application description and integrated new routers for events and subscriptions. - Modified requirements to include `croniter` for cron expression parsing. - Improved frontend navigation and components to accommodate new event and agent management features.
1 parent f446dce commit cf5727f

24 files changed

Lines changed: 1150 additions & 672 deletions

backend/app/api/agents.py

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,49 @@
1-
from fastapi import APIRouter, Depends, HTTPException
1+
import uuid
2+
from pathlib import Path
3+
4+
from fastapi import APIRouter, Depends, HTTPException, Query
25
from sqlalchemy import select
36
from sqlalchemy.ext.asyncio import AsyncSession
47

58
from app.db.database import get_db
6-
from app.models.tables import Agent, Team
9+
from app.config import settings
10+
from app.models.tables import Agent
711
from app.models.schemas import AgentCreate, AgentUpdate, AgentOut
812

9-
router = APIRouter(prefix="/teams/{team_id}/agents", tags=["agents"])
10-
11-
12-
async def _ensure_team(team_id: str, db: AsyncSession) -> Team:
13-
team = await db.get(Team, team_id)
14-
if not team:
15-
raise HTTPException(404, "Team not found")
16-
return team
13+
router = APIRouter(prefix="/agents", tags=["agents"])
1714

1815

1916
@router.get("", response_model=list[AgentOut])
20-
async def list_agents(team_id: str, db: AsyncSession = Depends(get_db)):
21-
await _ensure_team(team_id, db)
22-
result = await db.execute(
23-
select(Agent).where(Agent.team_id == team_id).order_by(Agent.created_at)
24-
)
17+
async def list_agents(
18+
group: str | None = Query(None),
19+
db: AsyncSession = Depends(get_db),
20+
):
21+
query = select(Agent).order_by(Agent.created_at)
22+
if group:
23+
query = query.where(Agent.group == group)
24+
result = await db.execute(query)
2525
return result.scalars().all()
2626

2727

2828
@router.post("", response_model=AgentOut, status_code=201)
29-
async def create_agent(team_id: str, body: AgentCreate, db: AsyncSession = Depends(get_db)):
30-
await _ensure_team(team_id, db)
29+
async def create_agent(body: AgentCreate, db: AsyncSession = Depends(get_db)):
30+
agent_id = uuid.uuid4().hex[:12]
31+
workspace = body.workspace_path.strip()
32+
if not workspace:
33+
workspace = str(settings.workspace_root / agent_id)
34+
Path(workspace).mkdir(parents=True, exist_ok=True)
35+
3136
agent = Agent(
32-
team_id=team_id,
37+
id=agent_id,
3338
name=body.name,
39+
group=body.group,
3440
role=body.role,
3541
description=body.description,
3642
model=body.model,
3743
system_prompt=body.system_prompt,
3844
skills=body.skills,
3945
mcp_config_path=body.mcp_config_path,
46+
workspace_path=workspace,
4047
)
4148
db.add(agent)
4249
await db.commit()
@@ -45,19 +52,17 @@ async def create_agent(team_id: str, body: AgentCreate, db: AsyncSession = Depen
4552

4653

4754
@router.get("/{agent_id}", response_model=AgentOut)
48-
async def get_agent(team_id: str, agent_id: str, db: AsyncSession = Depends(get_db)):
49-
await _ensure_team(team_id, db)
55+
async def get_agent(agent_id: str, db: AsyncSession = Depends(get_db)):
5056
agent = await db.get(Agent, agent_id)
51-
if not agent or agent.team_id != team_id:
57+
if not agent:
5258
raise HTTPException(404, "Agent not found")
5359
return agent
5460

5561

5662
@router.put("/{agent_id}", response_model=AgentOut)
57-
async def update_agent(team_id: str, agent_id: str, body: AgentUpdate, db: AsyncSession = Depends(get_db)):
58-
await _ensure_team(team_id, db)
63+
async def update_agent(agent_id: str, body: AgentUpdate, db: AsyncSession = Depends(get_db)):
5964
agent = await db.get(Agent, agent_id)
60-
if not agent or agent.team_id != team_id:
65+
if not agent:
6166
raise HTTPException(404, "Agent not found")
6267
for field, value in body.model_dump(exclude_unset=True).items():
6368
setattr(agent, field, value)
@@ -67,10 +72,9 @@ async def update_agent(team_id: str, agent_id: str, body: AgentUpdate, db: Async
6772

6873

6974
@router.delete("/{agent_id}", status_code=204)
70-
async def delete_agent(team_id: str, agent_id: str, db: AsyncSession = Depends(get_db)):
71-
await _ensure_team(team_id, db)
75+
async def delete_agent(agent_id: str, db: AsyncSession = Depends(get_db)):
7276
agent = await db.get(Agent, agent_id)
73-
if not agent or agent.team_id != team_id:
77+
if not agent:
7478
raise HTTPException(404, "Agent not found")
7579
await db.delete(agent)
7680
await db.commit()

backend/app/api/events.py

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
"""Event Gateway API: webhook 接收 + 事件日志查询。"""
1+
"""Event Gateway API: webhook 接收 + 手动触发 + 事件目录 + 事件日志查询。"""
22

3+
from pydantic import BaseModel
34
from fastapi import APIRouter, Depends, Request, HTTPException, Query
45
from sqlalchemy import select
56
from sqlalchemy.ext.asyncio import AsyncSession
@@ -8,11 +9,22 @@
89
from app.db.database import get_db
910
from app.models.tables import Subscription, EventLog
1011
from app.models.schemas import EventLogOut
11-
from app.services.event_normalizer import detect_and_normalize
12+
from app.services.event_normalizer import (
13+
detect_and_normalize,
14+
get_event_catalog,
15+
_make_event,
16+
)
1217
from app.services.event_router import match_subscriptions
1318
from app.services.event_dispatcher import dispatch_event
1419

15-
router = APIRouter(prefix="/api/events", tags=["events"])
20+
router = APIRouter(prefix="/events", tags=["events"])
21+
22+
23+
class ManualEventBody(BaseModel):
24+
event_type: str
25+
source: str = "manual/test"
26+
subject: str = ""
27+
data: dict = {}
1628

1729

1830
@router.post("/webhook/{source}")
@@ -53,6 +65,43 @@ async def receive_webhook(
5365
}
5466

5567

68+
@router.get("/catalog")
69+
async def event_catalog():
70+
"""返回系统支持的所有事件源和事件类型。"""
71+
return get_event_catalog()
72+
73+
74+
@router.post("/manual")
75+
async def trigger_manual_event(
76+
body: ManualEventBody,
77+
db: AsyncSession = Depends(get_db),
78+
):
79+
"""手动触发一个事件,用于测试/模拟。"""
80+
event = _make_event(
81+
source=body.source,
82+
event_type=body.event_type,
83+
subject=body.subject,
84+
data=body.data,
85+
)
86+
87+
subs_result = await db.execute(
88+
select(Subscription).where(Subscription.enabled == True) # noqa: E712
89+
)
90+
subscriptions = list(subs_result.scalars().all())
91+
92+
matched_ids = match_subscriptions(event, subscriptions)
93+
94+
event_log = await dispatch_event(event, matched_ids, db)
95+
96+
return {
97+
"event_id": event_log.id,
98+
"type": event.get("type"),
99+
"source": event.get("source"),
100+
"matched_agents": matched_ids,
101+
"status": event_log.status,
102+
}
103+
104+
56105
@router.get("", response_model=list[EventLogOut])
57106
async def list_events(
58107
source: str | None = Query(None),

backend/app/api/runs.py

Lines changed: 0 additions & 158 deletions
This file was deleted.

backend/app/api/subscriptions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from app.models.tables import Agent, Subscription
99
from app.models.schemas import SubscriptionCreate, SubscriptionUpdate, SubscriptionOut
1010

11-
router = APIRouter(prefix="/api/agents/{agent_id}/subscriptions", tags=["subscriptions"])
11+
router = APIRouter(prefix="/agents/{agent_id}/subscriptions", tags=["subscriptions"])
1212

1313

1414
async def _ensure_agent(agent_id: str, db: AsyncSession) -> Agent:

0 commit comments

Comments
 (0)