架构重构(候选 1-6): - storage/repository.py 按域拆分为包(target/scenario/run/campaign/result) - storage/db.py 按域拆分为包(eval/campaign/file/model_config/intelligent_eval) - intelligent_eval/lifecycle.py 按状态机阶段拆分为包 - services/runs.py 编排逻辑下沉 - Campaigns.tsx 拆分为 campaigns/ 子组件 测试补全(候选 7): 前端(+125 用例,107→232): - utils/ 纯函数:date/campaignTime/ruleLabels/fileTree/fileFormat/colors - stores/tabStore 状态管理 - 核心组件:FormDrawer/PageWrapper/ChatBubble/GeneratedMessages/SectionHeader/StatCard/TurnList - 业务组件:CaseBlock/CaseDetail/RuleOverview/WindowTimeline/RunList/TabBar/CampaignRunTimeline - 文件管理:FileCategoryTree/FileTable - hooks:sessionReducer/useFiles/useRunSession 后端(+38 用例,916→954): - targets API CRUD + 404 路径 - WebSocket 连接管理器 - proxy 头部重写(CSP/X-Frame-Options) - target 仓储 update 方法 - app 健康检查 + SPA 404 - scenarios 模板端点 + 404 - files API 边缘分支(404 场景 + 500 兜底) - files service update_category - 智能评估状态机迁移测试 门禁状态: - 前端:tsc 干净 + 232 passed - 后端:954 passed + ruff 全绿
138 lines
4.6 KiB
Python
138 lines
4.6 KiB
Python
"""Plan management phase: create, plan, approve/reject, cancel, delete, reads."""
|
||
|
||
from typing import Any, Optional
|
||
|
||
from sqlmodel import Session
|
||
|
||
from agenteval.intelligent_eval.lifecycle._core import (
|
||
IntelligentEvalNotFoundError,
|
||
get_or_raise,
|
||
resolve_write,
|
||
transition,
|
||
)
|
||
from agenteval.intelligent_eval.models import IntelligentEval, IntelligentEvalStatus
|
||
from agenteval.intelligent_eval.repository import IntelligentEvalRepository
|
||
from agenteval.storage.db import IntelligentEvalDB, utc_now
|
||
from agenteval.storage.repository import TargetRepository
|
||
|
||
|
||
def create_eval(
|
||
session: Session,
|
||
*,
|
||
name: str,
|
||
target_id: str,
|
||
goal: str,
|
||
seeds: dict[str, Any],
|
||
intent: str,
|
||
role_description: str,
|
||
time_window_hours: int = 24,
|
||
) -> IntelligentEval:
|
||
"""创建智能评估并直接进入 planning 状态(draft → planning 一步完成)。"""
|
||
from agenteval.intelligent_eval.config_snapshot import save_snapshot
|
||
|
||
if TargetRepository(session).get(target_id) is None:
|
||
raise IntelligentEvalNotFoundError(f"target {target_id} not found")
|
||
|
||
repo = IntelligentEvalRepository(session)
|
||
ev = repo._create(
|
||
IntelligentEval(
|
||
name=name,
|
||
target_id=target_id,
|
||
status=IntelligentEvalStatus.PLANNING,
|
||
goal=goal,
|
||
seeds=seeds,
|
||
intent=intent,
|
||
role_description=role_description,
|
||
time_window_hours=time_window_hours,
|
||
created_at=utc_now(),
|
||
updated_at=utc_now(),
|
||
)
|
||
)
|
||
|
||
# Save config snapshot
|
||
eval_db = session.get(IntelligentEvalDB, ev.id)
|
||
if eval_db:
|
||
save_snapshot(eval_db, "created", "user", session)
|
||
|
||
return ev
|
||
|
||
|
||
def submit_plan(session: Session, eval_id: str, plan: dict[str, Any]) -> IntelligentEval:
|
||
"""OpenClaw 提交粗计划:planning → pending_approval。"""
|
||
from agenteval.intelligent_eval.config_snapshot import save_snapshot
|
||
|
||
repo = IntelligentEvalRepository(session)
|
||
result = repo._submit_plan_if_planning(eval_id, plan)
|
||
ev = resolve_write(
|
||
eval_id,
|
||
result,
|
||
expected=IntelligentEvalStatus.PLANNING,
|
||
target=IntelligentEvalStatus.PENDING_APPROVAL,
|
||
)
|
||
|
||
# Save config snapshot
|
||
eval_db = session.get(IntelligentEvalDB, eval_id)
|
||
if eval_db:
|
||
save_snapshot(eval_db, "plan_submitted", "openclaw", session)
|
||
|
||
return ev
|
||
|
||
|
||
def approve(session: Session, eval_id: str) -> IntelligentEval:
|
||
"""用户批准:pending_approval → executing。"""
|
||
repo = IntelligentEvalRepository(session)
|
||
ev = get_or_raise(repo, eval_id)
|
||
return transition(repo, ev, IntelligentEvalStatus.EXECUTING)
|
||
|
||
|
||
def reject(session: Session, eval_id: str, feedback: str) -> IntelligentEval:
|
||
"""用户打回:pending_approval → planning(附反馈)。"""
|
||
repo = IntelligentEvalRepository(session)
|
||
result = repo._reject_plan_if_pending(eval_id, feedback)
|
||
return resolve_write(
|
||
eval_id,
|
||
result,
|
||
expected=IntelligentEvalStatus.PENDING_APPROVAL,
|
||
target=IntelligentEvalStatus.PLANNING,
|
||
)
|
||
|
||
|
||
def cancel(session: Session, eval_id: str) -> IntelligentEval:
|
||
"""用户取消:pending_approval / executing → cancelled。"""
|
||
repo = IntelligentEvalRepository(session)
|
||
ev = get_or_raise(repo, eval_id)
|
||
return transition(repo, ev, IntelligentEvalStatus.CANCELLED)
|
||
|
||
|
||
def delete_eval(session: Session, eval_id: str) -> IntelligentEval:
|
||
"""逻辑删除:completed / cancelled / failed → deleted。幂等:已删除直接返回。"""
|
||
repo = IntelligentEvalRepository(session)
|
||
ev = repo.get_including_deleted(eval_id)
|
||
if ev is None:
|
||
raise IntelligentEvalNotFoundError(f"intelligent eval {eval_id} not found")
|
||
if ev.status is IntelligentEvalStatus.DELETED:
|
||
return ev
|
||
return transition(repo, ev, IntelligentEvalStatus.DELETED)
|
||
|
||
|
||
def get_eval(session: Session, eval_id: str) -> IntelligentEval:
|
||
repo = IntelligentEvalRepository(session)
|
||
return get_or_raise(repo, eval_id)
|
||
|
||
|
||
def list_evals(session: Session) -> list[IntelligentEval]:
|
||
return IntelligentEvalRepository(session).list_all()
|
||
|
||
|
||
def list_evals_page(
|
||
session: Session, offset: int, limit: int, status: Optional[str] = None
|
||
) -> tuple[list[IntelligentEval], int, dict[str, int]]:
|
||
"""Return one page of evals (newest first), total count, and per-status counts."""
|
||
repo = IntelligentEvalRepository(session)
|
||
return repo.list_page(offset, limit, status), repo.count(), repo.count_by_status()
|
||
|
||
|
||
def eval_status_counts(session: Session) -> dict[str, int]:
|
||
"""Count evaluations per status (for the list page stat bar)."""
|
||
return IntelligentEvalRepository(session).count_by_status()
|