auto-sync: 2026-05-17 00:46:17

This commit is contained in:
cfdaily
2026-05-17 00:46:17 +08:00
parent 505f4fae7f
commit 7d9b8c9511
+186
View File
@@ -0,0 +1,186 @@
"""API 路由 — 黑板 CRUD"""
from __future__ import annotations
import json
from pathlib import Path
from typing import Any, Dict, List, Optional
from fastapi import APIRouter, HTTPException, Query
from src.blackboard.operations import Blackboard
from src.blackboard.models import Task, Review
from src.blackboard.queries import Queries
from src.blackboard.db import VALID_STATUSES, VALID_TRANSITIONS, COMMENT_TYPES, OUTPUT_TYPES
router = APIRouter(prefix="/api/projects/{project_id}", tags=["blackboard"])
def _bb(project_id: str) -> Blackboard:
import os
root = Path(os.environ.get("BLACKBOARD_ROOT",
str(Path.home() / ".sanguo_projects" / "sanguo_moziplus_v2" / "projects")))
return Blackboard(root / project_id / "blackboard.db")
def _q(project_id: str) -> Queries:
import os
root = Path(os.environ.get("BLACKBOARD_ROOT",
str(Path.home() / ".sanguo_projects" / "sanguo_moziplus_v2" / "projects")))
return Queries(root / project_id / "blackboard.db")
# --- Tasks ---
@router.get("/tasks")
async def list_tasks(project_id: str,
status: Optional[str] = None,
assignee: Optional[str] = None):
bb = _bb(project_id)
tasks = bb.list_tasks(status=status, assignee=assignee)
return {"tasks": [_task_to_dict(t) for t in tasks]}
@router.get("/tasks/{task_id}")
async def get_task(project_id: str, task_id: str):
bb = _bb(project_id)
task = bb.get_task(task_id)
if not task:
raise HTTPException(404, f"Task not found: {task_id}")
return _task_to_dict(task)
@router.post("/tasks")
async def create_task(project_id: str, body: Dict[str, Any]):
bb = _bb(project_id)
task = Task(
id=body["id"], title=body["title"],
description=body.get("description"),
task_type=body.get("task_type", "coding"),
priority=body.get("priority", 5),
assignee=body.get("assignee"),
assigned_by=body.get("assigned_by", "user"),
depends_on=json.dumps(body["depends_on"]) if "depends_on" in body else None,
risk_level=body.get("risk_level", "standard"),
)
bb.create_task(task)
return {"ok": True, "task_id": task.id}
@router.post("/tasks/{task_id}/claim")
async def claim_task(project_id: str, task_id: str, body: Dict[str, Any]):
bb = _bb(project_id)
if not bb.claim_task(task_id, body["agent"]):
raise HTTPException(409, "Claim failed (already claimed or wrong assignee)")
return {"ok": True}
@router.post("/tasks/{task_id}/status")
async def update_status(project_id: str, task_id: str, body: Dict[str, Any]):
bb = _bb(project_id)
if not bb.update_task_status(task_id, body["status"],
agent=body.get("agent")):
raise HTTPException(409, f"Invalid transition to {body['status']}")
return {"ok": True}
# --- Comments ---
@router.get("/tasks/{task_id}/comments")
async def get_comments(project_id: str, task_id: str,
comment_type: Optional[str] = None):
bb = _bb(project_id)
comments = bb.get_comments(task_id, comment_type=comment_type)
return {"comments": [dict(c.__dict__) for c in comments]}
@router.post("/tasks/{task_id}/comments")
async def add_comment(project_id: str, task_id: str, body: Dict[str, Any]):
bb = _bb(project_id)
cid = bb.add_comment(task_id, body["author"], body["body"],
comment_type=body.get("comment_type", "general"),
mentions=body.get("mentions"))
return {"ok": True, "comment_id": cid}
# --- Outputs ---
@router.get("/tasks/{task_id}/outputs")
async def get_outputs(project_id: str, task_id: str):
bb = _bb(project_id)
return {"outputs": [dict(o.__dict__) for o in bb.get_outputs(task_id)]}
@router.post("/tasks/{task_id}/outputs")
async def write_output(project_id: str, task_id: str, body: Dict[str, Any]):
bb = _bb(project_id)
oid = bb.write_output(task_id, body["agent"], body["type"],
body["title"], content_path=body.get("path"),
summary=body.get("summary"),
metadata=body.get("metadata"))
return {"ok": True, "output_id": oid}
# --- Decisions ---
@router.post("/tasks/{task_id}/decisions")
async def add_decision(project_id: str, task_id: str, body: Dict[str, Any]):
bb = _bb(project_id)
did = bb.add_decision(task_id, body["decider"], body["decision"],
body["rationale"],
alternatives=body.get("alternatives"))
return {"ok": True, "decision_id": did}
# --- Observations ---
@router.post("/tasks/{task_id}/observations")
async def add_observation(project_id: str, task_id: str, body: Dict[str, Any]):
bb = _bb(project_id)
oid = bb.add_observation(task_id, body["observer"], body["body"],
severity=body.get("severity", "info"))
return {"ok": True, "observation_id": oid}
# --- Reviews ---
@router.get("/tasks/{task_id}/reviews")
async def get_reviews(project_id: str, task_id: str):
bb = _bb(project_id)
return {"reviews": [dict(r.__dict__) for r in bb.get_reviews(task_id)]}
@router.post("/tasks/{task_id}/reviews")
async def add_review(project_id: str, task_id: str, body: Dict[str, Any]):
bb = _bb(project_id)
review = Review(
id=body["id"], task_id=task_id, reviewer=body["reviewer"],
review_type=body["review_type"], verdict=body["verdict"],
summary=body["summary"], confidence=body.get("confidence"),
round=body.get("round", 1), max_rounds=body.get("max_rounds", 3),
)
bb.add_review(review)
return {"ok": True, "review_id": review.id}
# --- Events ---
@router.get("/events")
async def get_events(project_id: str, limit: int = Query(50, le=200)):
q = _q(project_id)
return {"events": q.recent_events(limit)}
# --- Summary ---
@router.get("/summary")
async def task_summary(project_id: str):
q = _q(project_id)
return {"summary": q.task_summary()}
# --- Helper ---
def _task_to_dict(t: Task) -> Dict[str, Any]:
d = {k: v for k, v in t.__dict__.items() if v is not None}
return d