From cf4529720bc4cd4f75b23d85db3bbdef48a97f30 Mon Sep 17 00:00:00 2001 From: cfdaily Date: Tue, 19 May 2026 13:54:58 +0800 Subject: [PATCH] auto-sync: 2026-05-19 13:54:58 --- src/api/checkpoint_routes.py | 97 ++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 src/api/checkpoint_routes.py diff --git a/src/api/checkpoint_routes.py b/src/api/checkpoint_routes.py new file mode 100644 index 0000000..4280baa --- /dev/null +++ b/src/api/checkpoint_routes.py @@ -0,0 +1,97 @@ +"""Checkpoint API — M3 人工确认点 + +Agent 创建 checkpoint → 用户 approve/reject → 后端自动推进 task 状态 +""" + +from __future__ import annotations + +from fastapi import APIRouter, HTTPException +from pydantic import BaseModel +from typing import Optional + +from src.blackboard.operations import Blackboard +from src.blackboard.registry import ProjectRegistry + +router = APIRouter(prefix="/api/projects/{project_id}/tasks/{task_id}/checkpoints", tags=["checkpoints"]) + + +# ── 请求模型 ── + +class CreateCheckpointRequest(BaseModel): + type: str # verify | decision | action + title: str + payload: dict + description: Optional[str] = None + id: Optional[str] = None # 可选自定义 ID + + +class ResolveCheckpointRequest(BaseModel): + resolved_by: str = "user" + note: Optional[str] = None + + +# ── 工具 ── + +def _bb(project_id: str) -> Blackboard: + registry = ProjectRegistry() + db_path = registry.get_db_path(project_id) + if not db_path: + raise HTTPException(status_code=404, detail="Project not found") + return Blackboard(db_path) + + +# ── API ── + +@router.get("") +def list_checkpoints(project_id: str, task_id: str): + """列出 task 的所有 checkpoint""" + bb = _bb(project_id) + cps = bb.list_checkpoints(task_id) + return {"checkpoints": cps} + + +@router.post("") +def create_checkpoint(project_id: str, task_id: str, req: CreateCheckpointRequest): + """Agent 创建 checkpoint""" + if req.type not in ("verify", "decision", "action"): + raise HTTPException(status_code=400, detail=f"Invalid checkpoint type: {req.type}") + + bb = _bb(project_id) + # 验证 task 存在 + task = bb.get_task(task_id) + if not task: + raise HTTPException(status_code=404, detail="Task not found") + + result = bb.create_checkpoint( + task_id=task_id, + cp_type=req.type, + title=req.title, + payload=req.payload, + description=req.description, + checkpoint_id=req.id, + ) + return {"ok": True, **result} + + +@router.post("/{checkpoint_id}/approve") +def approve_checkpoint(project_id: str, task_id: str, checkpoint_id: str, req: ResolveCheckpointRequest): + """用户通过 checkpoint → 自动推进 task 状态""" + bb = _bb(project_id) + result = bb.resolve_checkpoint(checkpoint_id, "approve", req.resolved_by, req.note) + if result is None: + raise HTTPException(status_code=404, detail="Checkpoint not found") + if "error" in result: + raise HTTPException(status_code=400, detail=result["error"]) + return {"ok": True, **result} + + +@router.post("/{checkpoint_id}/reject") +def reject_checkpoint(project_id: str, task_id: str, checkpoint_id: str, req: ResolveCheckpointRequest): + """用户驳回 checkpoint → task 回到 working""" + bb = _bb(project_id) + result = bb.resolve_checkpoint(checkpoint_id, "reject", req.resolved_by, req.note) + if result is None: + raise HTTPException(status_code=404, detail="Checkpoint not found") + if "error" in result: + raise HTTPException(status_code=400, detail=result["error"]) + return {"ok": True, **result}