AgentEvalTool/backend/agenteval/web/routers/openclaw_cron_pool.py
sinohqb 2dd023fdd9
All checks were successful
CI / test (push) Successful in 4m2s
docs(intelligent-eval): align domain language with trigger-driven execution (ADR-0009)
方案③落地后,智能评估执行机制从'常驻 cron 每分钟自唤醒'改为'平台每 60s
扫描入队 + 按需触发无状态 headless agent'(触发式执行)。对齐领域语言:
- CONTEXT.md:Cron 池/工作单元(Worker)标 deprecated;新增触发式执行词条;
  修正时间窗口(cron 自唤醒→平台扫描时段到期)、任务队列(消费端)、决策日志
- ADR-0009 新增:记录触发式执行取代 cron 池的决策(原因:cron 需外部 channel,
  OpenClaw webchat 非 channel 账号无法 delivery);ADR-0007 标 superseded
- 代码标 deprecated:cron_pool / fault_tolerance / openclaw_cron_pool 路由 /
  CronPoolMonitor 页(导航入口已从 App.tsx 移除,监控由 TaskQueueMonitor 承担)
895 passed, vitest 19 passed
2026-08-17 16:16:12 +08:00

161 lines
5.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""API routes for OpenClaw cron pool management.
DEPRECATED (ADR-0009): 智能评估已改为触发式执行cron 池不再使用worker cron 已禁用)。
端点遗留保留仅供回溯监控职责由前端任务队列页TaskQueueMonitor承担。
"""
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field
from sqlmodel import Session
from agenteval.intelligent_eval import cron_pool
from agenteval.intelligent_eval.openclaw_client import OpenClawClient
from agenteval.web.deps import get_db
router = APIRouter()
class ScaleRequest(BaseModel):
target_size: int = Field(ge=1, le=50)
class HeartbeatRequest(BaseModel):
status: str # idle / busy
current_eval_id: str | None = None
@router.get("/cron-pool")
async def get_cron_pool_status(session: Session = Depends(get_db)) -> dict:
"""Get cron pool status."""
status = cron_pool.get_pool_status(session)
return {"pool": status}
@router.post("/cron-pool/scale")
async def scale_cron_pool(request: ScaleRequest, session: Session = Depends(get_db)) -> dict:
"""Manually scale cron pool to target size."""
client = OpenClawClient()
return await cron_pool.scale_to(request.target_size, session, client)
@router.post("/cron-pool/sync")
async def sync_cron_states(session: Session = Depends(get_db)) -> dict:
"""Sync cron states from OpenClaw to platform DB."""
client = OpenClawClient()
synced = await cron_pool.sync_cron_states(session, client)
return {"success": True, "synced": synced}
@router.post("/cron-pool/auto-scale")
async def auto_scale_pool(session: Session = Depends(get_db)) -> dict:
"""Trigger auto-scaling based on current load."""
client = OpenClawClient()
scaled_up, scaled_down = await cron_pool.auto_scale(session, client)
return {
"success": True,
"scaled_up": scaled_up,
"scaled_down": scaled_down,
}
@router.post("/crons/{cron_id}/heartbeat")
async def report_heartbeat(
cron_id: str,
request: HeartbeatRequest,
session: Session = Depends(get_db),
) -> dict:
"""Report cron heartbeat. Updates last_active_at and current status."""
try:
cron_pool.heartbeat(cron_id, request.status, request.current_eval_id, session)
except LookupError as e:
raise HTTPException(status_code=404, detail=str(e)) from e
return {"success": True}
@router.get("/cron-pool/metrics")
async def get_cron_pool_metrics(session: Session = Depends(get_db)) -> dict:
"""Get cron pool metrics."""
from agenteval.intelligent_eval.metrics import get_all_metrics
metrics = get_all_metrics(session)
return {"metrics": metrics}
@router.post("/cron-pool/check-alerts")
async def check_alerts(session: Session = Depends(get_db)) -> dict:
"""Check alert rules and create alerts if triggered."""
from agenteval.intelligent_eval.alerts import AlertManager
client = OpenClawClient()
manager = AlertManager(session, openclaw_client=client)
alerts = manager.check_rules()
# T7: link each newly-created alert to auto-scale (best-effort).
for alert in alerts:
await manager.maybe_autoscale(alert)
return {
"success": True,
"alerts_triggered": len(alerts),
"alerts": [
{
"id": alert.id,
"alert_type": alert.alert_type,
"severity": alert.severity,
"message": alert.message,
"metric_value": alert.metric_value,
"threshold": alert.threshold,
"created_at": alert.created_at.isoformat(),
}
for alert in alerts
],
}
@router.get("/cron-pool/alerts")
async def get_alert_history(
limit: int = 100,
unresolved_only: bool = False,
session: Session = Depends(get_db),
) -> dict:
"""Get alert history."""
from agenteval.intelligent_eval.alerts import AlertManager
manager = AlertManager(session)
if unresolved_only:
alerts = manager.get_unresolved_alerts()
else:
alerts = manager.get_alert_history(limit=limit)
return {
"alerts": [
{
"id": alert.id,
"alert_type": alert.alert_type,
"severity": alert.severity,
"message": alert.message,
"metric_value": alert.metric_value,
"threshold": alert.threshold,
"created_at": alert.created_at.isoformat(),
"resolved_at": alert.resolved_at.isoformat() if alert.resolved_at else None,
"webhook_sent": alert.webhook_sent,
}
for alert in alerts
]
}
@router.post("/cron-pool/alerts/{alert_id}/resolve")
async def resolve_alert(alert_id: str, session: Session = Depends(get_db)) -> dict:
"""Resolve an alert."""
from agenteval.intelligent_eval.alerts import AlertManager
manager = AlertManager(session)
resolved = manager.resolve_alert(alert_id)
if not resolved:
raise HTTPException(status_code=404, detail=f"alert {alert_id} not found")
return {"success": True}