fix: delete agent 500 error + dynamic personality + deployment guide
- Fix delete agent 500: clean up FK records (agent_llm_logs, permissions, schedules, executions, team_members) and unbind goals/tasks before delete - Remove hardcoded personality templates in Android, replace with dynamic system prompt generation from name + description - Set promptSectionsEnabled=false to bypass PromptComposer for personality - Add Tencent Cloud Linux deployment guide (Docker Compose) - Accumulated backend service updates, frontend UI fixes, Android app changes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
"""
|
||||
Task API — 任务管理接口
|
||||
Task API — 任务管理接口 (含原子认领 + 依赖图 + Agent 状态)
|
||||
|
||||
参考 Claude Code src/utils/tasks.ts 的 API 设计
|
||||
"""
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from sqlalchemy.orm import Session
|
||||
@@ -9,6 +11,7 @@ from datetime import datetime
|
||||
import logging
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.task_system import TaskSystem, ClaimResult, AgentState, AgentStatus
|
||||
from app.api.auth import get_current_user
|
||||
from app.models.user import User
|
||||
from app.services import goal_service
|
||||
@@ -92,6 +95,42 @@ class TaskDependencyCheck(BaseModel):
|
||||
pending_dependencies: List[str] = []
|
||||
|
||||
|
||||
class ClaimTaskRequest(BaseModel):
|
||||
agent_id: str = Field(..., description="认领任务的 Agent 标识")
|
||||
check_busy: bool = Field(default=True, description="是否检查 Agent 忙碌状态")
|
||||
|
||||
|
||||
class ClaimTaskResponse(BaseModel):
|
||||
success: bool
|
||||
reason: Optional[str] = None
|
||||
task: Optional[TaskResponse] = None
|
||||
busy_with_tasks: List[str] = []
|
||||
blocked_by_tasks: List[str] = []
|
||||
|
||||
|
||||
class BlockTaskRequest(BaseModel):
|
||||
from_task_id: str = Field(..., description="阻塞方任务ID")
|
||||
to_task_id: str = Field(..., description="被阻塞方任务ID")
|
||||
|
||||
|
||||
class AgentStatusResponse(BaseModel):
|
||||
agent_id: str
|
||||
status: str # idle / busy
|
||||
current_tasks: List[str] = []
|
||||
|
||||
|
||||
class ReleaseTaskRequest(BaseModel):
|
||||
agent_id: str = Field(..., description="释放任务的 Agent 标识")
|
||||
|
||||
|
||||
class TaskCompleteRequest(BaseModel):
|
||||
result: Optional[Dict[str, Any]] = None
|
||||
|
||||
|
||||
class TaskFailRequest(BaseModel):
|
||||
error_message: str = ""
|
||||
|
||||
|
||||
# ──────────────────────────── Endpoints ────────────────────────────
|
||||
|
||||
@router.post("", response_model=TaskResponse, status_code=201)
|
||||
@@ -307,3 +346,160 @@ async def retry_task(
|
||||
error_message=str(e),
|
||||
)
|
||||
return goal_service.get_task(db, task_id)
|
||||
|
||||
|
||||
# ════════════════════ 任务系统增强 (参考 Claude Code task_system) ════════════════════
|
||||
|
||||
|
||||
def _get_task_system(db: Session) -> TaskSystem:
|
||||
return TaskSystem(db)
|
||||
|
||||
|
||||
@router.post("/{task_id}/claim", response_model=ClaimTaskResponse)
|
||||
def claim_task(
|
||||
task_id: str,
|
||||
data: ClaimTaskRequest,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""原子认领任务 (SELECT FOR UPDATE),检查依赖+Agent忙碌"""
|
||||
ts = _get_task_system(db)
|
||||
result = ts.claim_task(task_id=task_id, agent_id=data.agent_id, check_busy=data.check_busy)
|
||||
return ClaimTaskResponse(
|
||||
success=result.success,
|
||||
reason=result.reason,
|
||||
task=TaskResponse.model_validate(result.task) if result.task else None,
|
||||
busy_with_tasks=result.busy_with_tasks,
|
||||
blocked_by_tasks=result.blocked_by_tasks,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/block", status_code=200)
|
||||
def block_task(
|
||||
data: BlockTaskRequest,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""设置任务依赖: from_task 阻塞 to_task"""
|
||||
ts = _get_task_system(db)
|
||||
ok = ts.block_task(data.from_task_id, data.to_task_id)
|
||||
if not ok:
|
||||
from fastapi import HTTPException
|
||||
raise HTTPException(404, "任务不存在")
|
||||
return {"message": "ok", "from": data.from_task_id, "to": data.to_task_id}
|
||||
|
||||
|
||||
@router.delete("/block", status_code=200)
|
||||
def unblock_task(
|
||||
data: BlockTaskRequest,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""移除任务依赖"""
|
||||
ts = _get_task_system(db)
|
||||
ok = ts.unblock_task(data.from_task_id, data.to_task_id)
|
||||
if not ok:
|
||||
from fastapi import HTTPException
|
||||
raise HTTPException(404, "任务不存在")
|
||||
return {"message": "ok"}
|
||||
|
||||
|
||||
@router.get("/agent/{agent_id}/status", response_model=AgentStatusResponse)
|
||||
def get_agent_status(
|
||||
agent_id: str,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""获取 Agent 忙闲状态 (idle/busy)"""
|
||||
ts = _get_task_system(db)
|
||||
state = ts.get_agent_status(agent_id)
|
||||
return AgentStatusResponse(
|
||||
agent_id=state.agent_id,
|
||||
status=state.status.value,
|
||||
current_tasks=state.current_tasks,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/{task_id}/release", status_code=200)
|
||||
def release_task(
|
||||
task_id: str,
|
||||
data: ReleaseTaskRequest,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Agent 主动释放单个任务"""
|
||||
ts = _get_task_system(db)
|
||||
ok = ts.release_task(task_id, data.agent_id)
|
||||
if not ok:
|
||||
from fastapi import HTTPException
|
||||
raise HTTPException(404, "任务不存在或不属于该 Agent")
|
||||
return {"message": "ok", "task_id": task_id}
|
||||
|
||||
|
||||
@router.post("/agent/{agent_id}/unassign", status_code=200)
|
||||
def unassign_agent_tasks(
|
||||
agent_id: str,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""释放 Agent 所有未完成任务 (Agent 下线时调用)"""
|
||||
ts = _get_task_system(db)
|
||||
tasks = ts.unassign_agent_tasks(agent_id)
|
||||
return {"message": "ok", "unassigned_count": len(tasks), "task_ids": [t.id for t in tasks]}
|
||||
|
||||
|
||||
@router.post("/{task_id}/complete", response_model=TaskResponse)
|
||||
def complete_task(
|
||||
task_id: str,
|
||||
data: TaskCompleteRequest = TaskCompleteRequest(),
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""标记任务完成 (自动检查被阻塞任务)"""
|
||||
ts = _get_task_system(db)
|
||||
task = ts.complete_task(task_id, result=data.result)
|
||||
if not task:
|
||||
from fastapi import HTTPException
|
||||
raise HTTPException(404, "任务不存在")
|
||||
return TaskResponse.model_validate(task)
|
||||
|
||||
|
||||
@router.post("/{task_id}/fail", response_model=TaskResponse)
|
||||
def fail_task(
|
||||
task_id: str,
|
||||
data: TaskFailRequest = TaskFailRequest(),
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""标记任务失败"""
|
||||
ts = _get_task_system(db)
|
||||
task = ts.fail_task(task_id, error_message=data.error_message)
|
||||
if not task:
|
||||
from fastapi import HTTPException
|
||||
raise HTTPException(404, "任务不存在")
|
||||
return TaskResponse.model_validate(task)
|
||||
|
||||
|
||||
@router.get("/available/{goal_id}", response_model=List[TaskResponse])
|
||||
def get_available_tasks(
|
||||
goal_id: str,
|
||||
limit: int = Query(default=10, ge=1, le=100),
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""获取目标下所有可执行任务 (依赖满足 + 未被认领)"""
|
||||
ts = _get_task_system(db)
|
||||
tasks = ts.get_next_available_tasks(goal_id, limit=limit)
|
||||
return [TaskResponse.model_validate(t) for t in tasks]
|
||||
|
||||
|
||||
@router.get("/{task_id}/blockers", response_model=List[TaskResponse])
|
||||
def get_task_blockers(
|
||||
task_id: str,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""获取任务尚未完成的阻塞依赖"""
|
||||
ts = _get_task_system(db)
|
||||
blockers = ts.get_unresolved_blockers(task_id)
|
||||
return [TaskResponse.model_validate(t) for t in blockers]
|
||||
|
||||
Reference in New Issue
Block a user