auto-sync: 2026-05-17 00:46:17
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user